ネストされたセット+ PostgreSQL TRIGGER

挑戦する

ネストされたセットなどのツリーから選択を行うのがどれほど便利で、それを管理するのがどれほど便利でないか。 id-> parent_idのようなツリーを管理するのは便利ですが、選択に再帰を使用するのは便利ではなく、費用もかかりません。 ツリーの管理にモジュールを使用すると、問題の一部が削除されますが、データベースを操作するプロセスが完全に透過的ではないことは明らかです。 データを変更するには、いくつかの方法を使用して、ツリー内のノードの場所を変更します。その他の方法に加えて、トランザクションは問題ありません。 この矛盾は、次の2つの方法で解決できます。 最初の方法は、テーブルの構造を変更するときにプロシージャも変更する必要があるという点で不便です。また、テーブルを操作するときは、データへのすべての変更が直接のリクエストではなくプロシージャを通過するようにできるだけ注意する必要があります。 2番目の方法は、追加のブールフィールドを導入することでテーブルをやや難しくします。また、作業の最大限の透明性を実現することはできますが、「耳をかすめる」必要もあります。1番目の方法は、特にインターネット上のどこかで既に同様のソリューションがあります。現時点では私に関係がありますが、MySQLのアドオンは後で作成します。

テーブル

それで、どんなトリガーが必要でしょうか: 熊手: 2番目のポイントはより詳細です。更新前のトリガーでは、同じテーブルのレコードを更新できます。これは、トリガーの繰り返し呼び出し、および削除後に呼び出されるトリガーを必要とします。 トリガーからのリクエストが私たちから呼び出されるかどうかを理解するために、データではなくトリガーのパラメーター(フラグ)としてリクエストに渡す2つの追加のブールフィールドを導入します。 なぜ2つですか?後で説明しますが、いくつかのツリーが格納されることを考慮して、テーブルの構造をすぐに作成します。その理由を説明します。 口に泡があると証明する愚かな開発者の話を聞いて涙が出るのはおもしろいです。多くのノードがあり、ノードを更新するとツリー全体に影響することがあります。これはデータベースにとって非常に困難です。 はい、正確に。 私は主張しません。たった今、1つのツリーに膨大な数のノードがありません。 例:いくつかのフォーラムがあります。 フォーラムセクションには1'000件の投稿があり、各投稿には1'000件のコメントがあります。 合計コメント-1'000'000。したがって、フォーラムセクションはコメントのルートノードではなく、投稿が同じツリーのノードではないように、ツリーのセパレータにすぎません。 その結果、1,000個のコメントに対して1,000個の個別のツリーがあります。 更新は、最大1,000エントリ内でのみ発生します。 場合によっては、これがたくさんある場合、ツリーセパレーターは第1レベルのコメントになります。 その結果、ツリーの再構築はベースにそれほど負荷がかかりません。 数学の部分を学びましょう悲しいテーブル構造については話しましょう:SQLコード(1)
作成
   ns_tree(
     id SERIAL、
     left_key INTEGER NOT NULL、
     right_key INTEGER NOT NULL、
    レベルINTEGER NOT NULL DEFAULT 0、
    ツリーINTEGER NOT NULL、    
     parent_id INTEGER NOT NULL DEFAULT 0、
     _trigger_lock_update BOOLEAN NOT NULL DEFAULT FALSE、
     _trigger_for_delete BOOLEAN NOT NULL DEFAULT FALSE、
     field_1 ...、
     ...
主キー(id)
 );
    
実際には-_trigger_lock_update_trigger_for_deleteは補助フィールドであり、トランザクションが完了するまで関数を変更ツリーにブロックします:SQLコード(2)
関数の作成または置換lock_ns_tree(整数)
    ブール値ASを返します
 $ BODY $
 $ 1のtree_idエイリアスを宣言します。
     _id INTEGER;
開始
     SELECT ID
         INTO _id
         ns_treeから
         WHERE tree = tree_id FOR UPDATE;
     TRUEを返します。
終了
 $ BODY $
  言語 'plpgsql'揮発性
  費用100;
 ALTER FUNCTION lock_ns_tree(整数)OWNER TOユーザー。
    

レコードを作成

ツリーにノードを追加するための3つのオプションがあります。 同じ手順で、作成されたノードの宛先を決定します。 SQLコード(3)
 ns_tree_before_insert_func()関数の作成または置換
    リターンAS
 $ BODY $
デカール
     _left_key INTEGER;
     _level INTEGER;
     _tmp_left_key INTEGER;
     _tmp_right_key INTEGER;
     _tmp_level INTEGER;
     _tmp_id INTEGER;
     _tmp_parent_id INTEGER;
開始
     PERFORM lock_ns_tree(NEW.tree);
 -これらのフィールドをペンで設定することはできません:
     NEW._trigger_for_delete:= FALSE;
     NEW._trigger_lock_update:= FALSE;
     _left_key:= 0;
     _level:= 0;
 -親を指定した場合:
     NEW.parent_idがNULLではなく、NEW.parent_id> 0の場合
         SELECT right_key、「レベル」+1
             INTO _left_key、_level
             ns_treeから
             WHERE id = NEW.parent_id AND
                  ツリー= NEW.tree;
    終了IF;
 -左キーを指定した場合:
     NEW.left_keyがNULLではない場合
        NEW.left_key> 0 AND 
        (_left_keyはNULLまたは_left_key = 0)THEN
         SELECT id、left_key、right_key、 "level"、parent_id 
             INTO _tmp_id、_tmp_left_key、_tmp_right_key、_tmp_level、_tmp_parent_id
             ns_treeから
             WHEREツリー= NEW.tree AND(left_key = NEW.left_key OR right_key = NEW.left_key);
         _tmp_left_keyがNULLではなく、かつ_tmp_left_key> 0およびNEWの場合、left.key = _tmp_left_key THEN
             NEW.parent_id:= _tmp_parent_id;
             _left_key:= NEW.left_key;
             _level:= _tmp_level;
         ELSIF _tmp_left_keyはNULLではなく、_tmp_left_key> 0およびNEW.left_key = _tmp_right_key THEN
             NEW.parent_id:= _tmp_id;
             _left_key:= NEW.left_key;
             _level:= _tmp_level + 1;
        終了IF;
    終了IF;
 -親キーまたは左キーが指定されていない場合、または何も見つからなかった場合:
     _left_keyがNULLまたは_left_key = 0の場合
         SELECT MAX(right_key)+ 1
             INTO _left_key
             ns_treeから
             WHEREツリー= NEW.tree;
         _left_keyがNULLまたは_left_key = 0の場合
             _left_key:= 1;
        終了IF;
         _level:= 0;
         NEW.parent_id:= 0; 
    終了IF;
 -受信したノードのキーを設定します。
     NEW.left_key:= _left_key;
     NEW.right_key:= _left_key + 1;
     NEW。 "レベル":= _level;
 -挿入ポイントでツリーにブレークを形成します。
     ns_treeの更新
         SET left_key = left_key + 
             left_key> = _left_keyの場合 
              その後2 
              その他0 
             END、
             right_key = right_key + 2
             _trigger_lock_update = TRUE
         WHEREツリー= NEW.tree AND right_key> = _left_key;
    新しいリターン;
終了
 $ BODY $
  言語 'plpgsql'揮発性
  費用100;
 ALTER FUNCTION ns_tree_before_insert_func()OWNER TOユーザー。

 CREATE TRIGGER ns_tree_before_insert_tr
    挿入する前に
     ns_treeで
    各行ごと
     EXECUTE PROCEDURE ns_tree_before_insert_func();
    
今、いくつかの明確化:

レコードを編集

より正確には、このトリガーは変更可能なデータではなく、ツリーの構造でのみ機能します。何らかのアクションを強制する主なパラメーターは、 parent_idまたはleft_keyです (もちろん、トリガーの制御パラメーターとして_trigger_lock_updateを思い出してください)。アルゴリズムは単純です。その後、ツリーを再構築します。 SQLコード(4)
関数の作成または置換ns_tree_before_update_func()
  リターンAS
 $ BODY $
デカール
     _left_key INTEGER;
     _level INTEGER;
     _skew_tree INTEGER;
     _skew_level INTEGER;
     _skew_edit INTEGER;
     _tmp_left_key INTEGER;
     _tmp_right_key INTEGER;
     _tmp_level INTEGER;
     _tmp_id INTEGER;
     _tmp_parent_id INTEGER;
開始
     PERFORM lock_ns_tree(OLD.tree);
 -何でもする価値があるか:
     NEW._trigger_lock_update = TRUEの場合
         NEW._trigger_lock_update:= FALSE;
         IF NEW._trigger_for_delete = TRUE THEN
             NEW = OLD;
             NEW._trigger_for_delete = TRUE;
            新しいリターン;
        終了IF;
        新しいリターン;
    終了IF;
 -ユーザーが変更できないフィールドの値をリセットします。
     NEW._trigger_for_delete:= FALSE;
     NEW.tree:= OLD.tree;
     NEW.right_key:= OLD.right_key;
     NEW。 "レベル":= OLD。 "レベル";
     NEW.parent_idがNULLの場合NEW.parent_id:= 0; 終了IF;
 -ツリーの構造に関連する変更があるかどうかを確認します
     NEW.parent_id = OLD.parent_idおよびNEW.left_key = OLD.left_keyの場合
    その後
        新しいリターン;
    終了IF;
 -ツリーを再構築しています。先に進みましょう。
     _left_key:= 0;
     _level:= 0;
     _skew_tree:= OLD.right_key-OLD.left_key + 1;
 -移動先を決定します。
 -parent_idが変更された場合:
     NEW.parent_id <> OLD.parent_idの場合
 -別の悪にさらされている場合:
         NEW.parent_id> 0の場合
             SELECT right_key、レベル+ 1
                 INTO _left_key、_level
                 ns_treeから
                 WHERE id = NEW.parent_id AND tree = NEW.tree;
 -それ以外の場合、ツリーのルートに転送します。
        その他
             SELECT MAX(right_key)+ 1 
                 INTO _left_key
                 ns_treeから
                 WHEREツリー= NEW.tree;
             _level:= 0;
        終了IF;
 -突然親が移動したノードの範囲内にある場合は、以下を確認してください。
         _left_keyがNULLではない場合 
            _left_key> 0 AND
            _left_key> OLD.left_key AND
            _left_key <= OLD.right_key 
        その後
            NEW.parent_id:= OLD.parent_id;
            NEW.left_key:= OLD.left_key;
           新しいリターン;
        終了IF;
    終了IF;
 -left_keyが指定され、parent_idが指定されていない場合
     _left_keyがNULLまたは_left_key = 0の場合
         SELECT id、left_key、right_key、 "level"、parent_id 
             INTO _tmp_id、_tmp_left_key、_tmp_right_key、_tmp_level、_tmp_parent_id
             ns_treeから
             WHEREツリー= NEW.tree AND(right_key = NEW.left_key OR right_key = NEW.left_key-1)
             LIMIT 1;
         _tmp_left_keyがNULLではなく、_tmp_left_key> 0およびNEW.left_key-1 = _tmp_right_keyの場合
             NEW.parent_id:= _tmp_parent_id;
             _left_key:= NEW.left_key;
             _level:= _tmp_level;
         ELSIF _tmp_left_keyはNULLではなく、_tmp_left_key> 0およびNEW.left_key = _tmp_right_key THEN
             NEW.parent_id:= _tmp_id;
             _left_key:= NEW.left_key;
             _level:= _tmp_level + 1;
         ELSIF NEW.left_key = 1 THEN
             NEW.parent_id:= 0;
             _left_key:= NEW.left_key;
             _level:= 0;
        その他
            NEW.parent_id:= OLD.parent_id;
            NEW.left_key:= OLD.left_key;
           新しいリターン;
        終了IF;
    終了IF;
 -これで、ツリーの移動先がわかりました
         _skew_level:= _level-OLD。 "level";
     IF _left_key> OLD.left_key THEN
 -ツリーを上に移動する
         _skew_edit:= _left_key-OLD.left_key-_skew_tree;
         ns_treeの更新
             SET left_key = right_key <= OLD.right_keyの場合
                                  THEN left_key + _skew_edit
                                 その他の場合left_key> OLD.right_keyの場合
                                            THEN left_key-_skew_tree
                                            ELSE left_key
                                      終了
                             END、
                 "level" = right_key <= OLD.right_keyの場合 
                                  THEN "レベル" + _skew_level
                                  ELSE「レベル」
                             END、
                 right_key = right_key <= OLD.right_keyの場合 
                                  THEN right_key + _skew_edit
                                 その他のケースright_key <_left_keyの場合
                                            THEN right_key-_skew_tree
                                            ELSE right_key
                                      終了
                             END、
                 _trigger_lock_update = TRUE
             WHEREツリー= OLD.tree AND
                   right_key> OLD.left_key AND
                   left_key <_left_key AND
                   id <> OLD.id;
         _left_key:= _left_key-_skew_tree;
    その他
 -ツリーを下に移動する:
         _skew_edit:= _left_key-OLD.left_key;
         ns_treeの更新
            セット
                 right_key = left_key> = OLD.left_keyの場合
                                  THEN right_key + _skew_edit
                                 その他の場合right_key <OLD.left_keyの場合
                                            THEN right_key + _skew_tree
                                            ELSE right_key
                                      終了
                             END、
                 "level" =場合left_key> = OLD.left_keyの場合
                                  THEN "レベル" + _skew_level
                                  ELSE「レベル」
                             END、
                 left_key =ケースleft_key> = OLD.left_keyの場合
                                  THEN left_key + _skew_edit
                                 その他の場合left_key> = _left_key
                                            THEN left_key + _skew_tree
                                            ELSE left_key
                                      終了
                             END、
                  _trigger_lock_update = TRUE
             WHEREツリー= OLD.tree AND
                   right_key> = _left_key AND
                   left_key <OLD.right_key AND
                   id <> OLD.id;
    終了IF;
 -ツリーが再構築され、現在のノードのみが残りました
     NEW.left_key:= _left_key;
     NEW。 "レベル":= _level;
     NEW.right_key:= _left_key + _skew_tree-1;
    新しいリターン;
終了
 $ BODY $
    言語 'plpgsql'揮発性
    費用100;
 ALTER FUNCTION ns_tree_before_update_func()OWNER TOユーザー;

 CREATE TRIGGER ns_tree_before_update_tr
    更新前
     ns_treeで
    各行ごと
     EXECUTE PROCEDURE ns_tree_before_update_func();
    
説明:

レコードを削除

ここで最も難しいの除去。 まず、ノードを削除するための2つの原則があるためです。 第二に、トリガーの再帰呼び出しを制限するために削除要求でパラメーターを渡すことはできませんが、トリガーの再帰呼び出しは子孫を持つノードを削除する場合にのみ関連します。両方の削除原則に対してユニバーサルトリガーを作成することは有益ではありません。ロジックが多すぎます。 結局のところ、2つの異なるソリューションの方が優れています最初のソリューション(子孫を持つノードを削除する)では、次のアルゴリズムを使用します。 SQLコード(5)
関数の作成または置換ns_tree_after_delete_func()
    リターンAS
 $ BODY $
デカール
     _skew_tree INTEGER;
開始
     PERFORM lock_ns_tree(OLD.tree);
 -トリガーを実行する価値があるかどうかを確認します。
     OLD._trigger_for_delete = TRUEの場合は、OLDを返します。 終了IF;
 -子ノードの削除時にマークを付けます:
     ns_treeの更新
         SET _trigger_for_delete = TRUE、
             _trigger_lock_update = TRUE
        どこ
            ツリー= OLD.tree AND
             left_key> OLD.left_key AND
             right_key <OLD.right_key;
 -マークされたノードを削除します。
     ns_treeから削除
        どこ
            ツリー= OLD.tree AND
             left_key> OLD.left_key AND
             right_key <OLD.right_key;
 -キーのギャップを削除します。
     _skew_tree:= OLD.right_key-OLD.left_key + 1;
     ns_treeの更新
         SET left_key = left_key> OLD.left_keyの場合
                             THEN left_key-_skew_tree
                             ELSE left_key
                        END、
             right_key = right_key-_skew_tree、
             _trigger_lock_update = TRUE
         WHERE right_key> OLD.right_key AND
            ツリー= OLD.tree;
    戻る;
終了
 $ BODY $
    言語 'plpgsql'揮発性
    費用100;
 ALTER FUNCTION ns_tree_after_delete_func()OWNER TOユーザー;

 CREATE TRIGER ns_tree_after_delete_tr
    削除後
     ns_treeで
    各行ごと
     EXECUTE PROCEDURE ns_tree_after_delete_func();
    
2番目のソリューションでは、子ツリーを1レベル上に移動し、キーブレークを削除します。 SQLコード(6)
関数の作成または置換ns_tree_after_delete_2_func()
    リターンAS
 $ BODY $
デカール
開始
     PERFORM lock_ns_tree(OLD.tree);
 -キーのギャップを削除し、子ノードを移動します。
    ns_treeの更新
         SET left_key = left_key <OLD.left_keyの場合
                             THEN left_key
                            その他のケース(right_key <OLD.right_keyの場合)
                                       THEN left_key-1 
                                       ELSE left_key-2
                                 終了
                        END、
             「レベル」= right_key <OLD.right_keyの場合
                           次に「レベル」-1 
                            ELSE「レベル」
                       END、
             parent_id = right_key <OLD.right_key AND "level" = OLD.level + 1の場合
                            OLD.parent_id
                            ELSE parent_id
                         END、
             right_key = right_key <OLD.right_keyの場合
                              THEN right_key-1 
                              ELSE right_key-2
                         END、
             _trigger_lock_update = TRUE
         WHERE(right_key> OLD.right_keyまたは
             (left_key> OLD.left_key AND right_key <OLD.right_key))AND
            ツリー= OLD.tree;
    戻る;
終了
 $ BODY $
  言語 'plpgsql'揮発性
  費用100;
 ALTER FUNCTION ns_tree_after_delete_2_func()OWNER TOユーザー;

 CREATE TRIGER ns_tree_after_delete_2_tr
    削除後
     ns_treeで
    各行ごと
     EXECUTE PROCEDURE ns_tree_after_delete_2_func();
    
実際にはすべて。 インデックスを置くことだけが残っています(ここでSQLコマンドを書くのは面倒なので、ただ声を出して説明します)。 お楽しみください;-)



All Articles