weblog

技術的なメモ置き場。

閉包テーブルの更新(その1)

閉包テーブル(Closure Table)の更新について書く。

閉包テーブルについては以下を参照。 lab.kochlein.com

サンプルテーブル

CREATE TABLE folders (
      folder_id   SERIAL PRIMARY KEY
    , name        TEXT NOT NULL
);

CREATE TABLE ctable (
      ancestor    BIGINT NOT NULL -- 先祖
    , descendant  BIGINT NOT NULL -- 子孫
    , PRIMARY KEY (ancestor, descendant)
    , FOREIGN KEY (ancestor) REFERENCES folders(folder_id)
    , FOREIGN KEY (descendant) REFERENCES folders(folder_id)
);

サンプルデータ

構造

テーブル

folder_id name
1 フォルダ1
2 フォルダ2
3 フォルダ3
4 フォルダ4
5 フォルダ5
ancestor descendant
1 1
1 2
1 3
1 4
1 5
2 2
2 3
3 3
4 4
4 5
5 5

子孫の取得

-- フォルダ2の子孫
SELECT f.*
  FROM folders AS f INNER JOIN ctable AS ct
    ON f.folder_id = ct.descendant
 WHERE ct.ancestor = 2;

先祖の取得

-- フォルダ4の先祖
SELECT f.*
  FROM folders AS f INNER JOIN ctable AS ct
    ON f.folder_id = ct.ancestor
 WHERE ct.descendant = 4;

子の追加

-- フォルダ6を追加
INSERT INTO folders VALUES (6, 'フォルダ6');

-- フォルダ5にフォルダ6を追加
INSERT INTO ctable (ancestor, descendant)
-- フォルダ5を子孫として参照する行
SELECT ct.ancestor, 6
  FROM ctable AS ct
 WHERE ct.descendant = 5
UNION ALL
-- 自己参照の行
SELECT 6, 6;

葉ノードの削除

DELETE FROM ctable WHERE descendant = 3;

サブツリーの削除

DELETE 
  FROM ctable
 WHERE descendant IN (SELECT descendant
                        FROM ctable
                       WHERE ancestor = 2);

サブツリーの移動

-- フォルダ4をフォルダ3に移動する
-- 自己参照を除く、先祖と子孫を削除
DELETE 
  FROM ctable
 WHERE descendant IN (SELECT descendant
                        FROM ctable
                       WHERE ancestor = 4)
   AND ancestor   IN (SELECT ancestor
                        FROM ctable
                       WHERE descendant = 4
                         AND ancestor != descendant);

INSERT INTO ctable (ancestor, descendant)
SELECT super.ancestor, sub.descendant
  FROM ctable AS super
 CROSS JOIN ctable AS sub
 WHERE super.descendant = 3
   AND sub.ancestor = 4;

参考

SQLアンチパターン