2

次のような古典的な方法で構築された自己参照階層テーブルがあるとします。

CREATE TABLE test
(name text,id serial primary key,parent_id integer
references test);

insert into test (name,id,parent_id) values
('root1',1,NULL),('root2',2,NULL),('root1sub1',3,1),('root1sub2',4,1),('root
2sub1',5,2),('root2sub2',6,2);

testdb=# select * from test;

   name    | id | parent_id
-----------+----+-----------
 root1     |  1 |  
 root2     |  2 |  
 root1sub1 |  3 |         1
 root1sub2 |  4 |         1
 root2sub1 |  5 |         2
 root2sub2 |  6 |         2

私が今必要としているのは、テスト レコードの ID を取得し、添付されたすべてのレコード (指定されたレコードを含む) を複製する関数 (できればプレーン SQL) です。もちろん、複製されたレコードには新しい ID が必要です。望ましい結果は、たとえば次のようになります。

Select * from cloningfunction(2);

   name    | id | parent_id    
-----------+----+-----------
 root2     |  7 |  
 root2sub1 |  8 |         7
 root2sub2 |  9 |         7

ポインタはありますか?PostgreSQL 8.3 を使用しています。

4

4 に答える 4

6

この結果を再帰的にプルするのはトリッキーです (可能ではありますが)。ただし、通常はあまり効率的ではなく、この問題を解決するためのはるかに優れた方法があります。

基本的に、ツリーを一番上までたどる追加の列をテーブルに追加します。これを「アップチェーン」と呼びます。次のような単なる長い文字列です。

name | id | parent_id | upchain
root1 | 1 | NULL | 1:
root2 | 2 | NULL | 2:
root1sub1 | 3 | 1 | 1:3:
root1sub2 | 4 | 1 | 1:4:
root2sub1 | 5 | 2 | 2:5:
root2sub2 | 6 | 2 | 2:6:
root1sub1sub1 | 7 | 3 | 1:3:7:

テーブルでトリガーを使用して、このフィールドを更新し続けるのは非常に簡単です。(用語については申し訳ありませんが、私は常に SQL Server でこれを行ってきました)。レコードを追加または削除するか、parent_id フィールドを更新するたびに、ツリーのその部分のアップチェーン フィールドを更新するだけで済みます。親レコードのアップチェーンを取得し、現在のレコードの ID を追加するだけなので、これは簡単な作業です。すべての子レコードは、LIKE を使用して簡単に識別され、アップチェーン内の開始文字列を持つレコードをチェックします。

あなたが効果的に行っていることは、データを読み取るときに大きな節約のために少し余分な書き込みアクティビティを交換することです.

ツリー内の完全なブランチを選択したい場合は簡単です。ノード 1 の下にブランチが必要だとします。ノード 1 にはアップチェーン '1:' があるため、そのノードの下のツリーのブランチ内のノードには、'1:...' で始まるアップチェーンが必要であることがわかります。したがって、これを行うだけです:

SELECT *
FROM table
WHERE upchain LIKE '1:%'

これは非常に高速です (もちろん、アップチェーン フィールドにインデックスを付けます)。おまけとして、部分的なツリーの検索、ツリー内のレベルなど、多くのアクティビティが非常に簡単になります。

私は大規模な従業員のレポート階層を追跡するアプリケーションでこれを使用しましたが、ほとんどすべてのツリー構造 (部品の内訳など) に使用できます。

注意事項(興味のある方へ):

  • SQL コードのステップバイステップは示していませんが、原則を理解すれば、実装は非常に簡単です。私は優れたプログラマーではないので、経験から話しています。
  • テーブルに既にデータがある場合は、最初にアップチェーンを同期させるために 1 回限りの更新を行う必要があります。繰り返しますが、コードはトリガーの UPDATE コードと非常に似ているため、これは難しくありません。
  • この手法は、他の方法では見つけにくい循環参照を特定するのにも適しています。
于 2008-09-27T09:37:04.857 に答える
3

njreedの回答に似ていますが、より一般的なJoe Celkoの方法は次のとおりです。

于 2008-09-27T12:18:27.543 に答える
1

@Maximilian : そうです、実際の要件を忘れてしまいました。再帰的なストアド プロシージャはどうでしょうか。これが PostgreSQL で可能かどうかはわかりませんが、SQL Server の動作バージョンは次のとおりです。

CREATE PROCEDURE CloneNode
    @to_clone_id int, @parent_id int
AS
    SET NOCOUNT ON
    DECLARE @new_node_id int, @child_id int

    INSERT INTO test (name, parent_id) 
        SELECT name, @parent_id FROM test WHERE id = @to_clone_id
    SET @new_node_id = @@IDENTITY

    DECLARE @children_cursor CURSOR
    SET @children_cursor = CURSOR FOR 
        SELECT id FROM test WHERE parent_id = @to_clone_id

    OPEN @children_cursor
    FETCH NEXT FROM @children_cursor INTO @child_id
    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXECUTE CloneNode @child_id, @new_node_id
        FETCH NEXT FROM @children_cursor INTO @child_id
    END
    CLOSE @children_cursor
    DEALLOCATE @children_cursor

あなたの例はEXECUTE CloneNode 2, null(2番目のパラメーターは新しい親ノードです)によって実現されます。

于 2008-09-28T19:41:40.233 に答える
0

これは、Joe Celko による「SQL For Smarties」の演習のように聞こえます...

手元にコピーはありませんが、このような問題を解決する必要がある場合、かなり役立つ本だと思います。

于 2008-09-27T10:31:28.923 に答える