17

PostgreSQL 8.4.14 データベースを使用して、次の例のようなツリー構造を表すテーブルがあります。

CREATE TABLE unit (
    id bigint NOT NULL PRIMARY KEY,
    name varchar(64) NOT NULL,
    parent_id bigint,
    FOREIGN KEY (parent_id) REFERENCES unit (id)
);
INSERT INTO unit VALUES (1, 'parent', NULL), (2, 'child', 1)
                      , (3, 'grandchild A', 2), (4, 'grandchild B', 2);
 id |    name      | parent_id 
----+--------------+-----------
  1 | parent       |          
  2 | child        |         1
  3 | grandchild A |         2
  4 | grandchild B |         2

これらのユニットのアクセス制御リストを作成したいと考えています。各ユニットは独自の ACL を持っているか、独自の ACL を持つ最も近い先祖から継承しています。

CREATE TABLE acl (
    unit_id bigint NOT NULL PRIMARY KEY,
    FOREIGN KEY (unit_id) REFERENCES unit (id)
);
INSERT INTO acl VALUES (1), (4);
 unit_id 
---------
       1
       4

ビューを使用して、ユニットが祖先から ACL を継承しているかどうかを判断しています。

CREATE VIEW inheriting_acl AS
    SELECT u.id AS unit_id, COUNT(a.*) = 0 AS inheriting
    FROM unit AS u
    LEFT JOIN acl AS a ON a.unit_id = u.id
    GROUP BY u.id;
 unit_id | inheriting 
---------+------------
       1 | f
       2 | t
       3 | t
       4 | f

私の質問は、祖先から ACL を継承していない最も近いユニットを取得するにはどうすればよいですか? 期待される結果は、次のテーブル/ビューのようになります。

 unit_id | acl 
---------+------------
       1 | 1
       2 | 1
       3 | 1
       4 | 4
4

1 に答える 1

17

再帰CTEを使用したクエリでその作業を実行できます。PostgreSQL8.4以降が必要です

WITH RECURSIVE next_in_line AS (
    SELECT u.id AS unit_id, u.parent_id, a.unit_id AS acl
    FROM   unit u
    LEFT   JOIN acl a ON a.unit_id = u.id

    UNION  ALL
    SELECT n.unit_id, u.parent_id, a.unit_id
    FROM   next_in_line n
    JOIN   unit u ON u.id = n.parent_id AND n.acl IS NULL
    LEFT   JOIN acl a ON a.unit_id = u.id
    )
SELECT unit_id, acl
FROM   next_in_line
WHERE  acl IS NOT NULL
ORDER  BY unit_id

の2番目のレッグのブレーク条件UNIONはですn.acl IS NULL。これにより、クエリは、aclが見つかるとすぐにツリーのトラバースを停止します。
ファイナルでは、が見つかっSELECTた行のみを返します。aclVoilá。

余談ですが、id列名として一般的でわかりにくいものを使用するのはアンチパターンです。悲しいことに、一部のORMはデフォルトでそれを行います。それを呼び出すとunit_id、クエリで常にエイリアスを使用する必要はありません。

于 2012-11-01T21:02:09.023 に答える