PostgreSQL:查找元素的权限,遍历到根

PostgreSQL: Find permission for element, traverse up to root

这是我正在使用的数据库结构

Item
----
ID [PK]
Name
Desc

Links
-----
ID [FK]
LID [FK] -- Link ID
LType    -- Link Type (Parent, Alias)

Permission 
----------
ID [FK]
CanRead
CanWrite
CanDelete


Let's assume, we have the below data in the table
Item Table
-----------
ID  Name    Desc
=================
0   Root    Base Item
1   One     First
2   Two     Second
3   Three   Third
4   Four    Forth
5   Five    Fifth
6   Six     Sixth

Links Table
-----------
ID  LID     LType   
==================
1   0       Parent
2   0       Parent
3   1       Parent
4   2       Parent
5   4       Parent
6   5       Parent

0 
|- 1 
|   |- 3
|- 2
    |- 4
        |- 5
            |- 6

Permission Table
-----------------
ID  CanRead     CanWrite    CanDelete
=====================================
0   T           T           T
2   T           F           F
5   T           T           F
6   F           F           F

问题是,如果我想要6的权限,我可以直接查询Permission table并得到Read/Write/Delete值。 但是,如果我想要 4 的权限,它不存在于权限 table 中,所以我需要找到父级,即 2, 因为我有 2 的许可,所以我可以 return 它。

比较棘手, 如果我想要 3 的权限,我检查权限 table,它不存在,查看不存在的父项 (1), 寻找它的父级(0-Root),return 值。

这可以适用于任何级别,假设我们没有权限中的记录 2、5、6 table, 所以当我查找 6 时,我需要一直遍历到 root 以获得权​​限。

注意:我们始终拥有 Root 权限。

我希望这在 DB 层而不是应用程序层完成,因此在编写 SQL 查询(递归)或存储过程方面的任何帮助都会很棒。

谢谢!!

您可以为此使用 RECURSIVE CTE

WITH RECURSIVE Perms(ID, Name, ParentID, CanRead, CanWrite, CanDelete) AS (
   SELECT i.ID, i.Name, l.LID AS ParentID, p.CanRead, p.CanWrite, p.CanDelete
   FROM Item AS i
   LEFT JOIN Permission AS p ON i.ID = p.ID
   LEFT JOIN Links AS l ON i.ID = l.ID
),  GET_PERMS(ID, ParentID, CanRead, CanWrite, CanDelete) AS (
    -- Anchor member: Try to get Read/Write/Delete values from Permission table
    SELECT ID, ParentID, CanRead, CanWrite, CanDelete
    FROM Perms
    WHERE ID = 3

  UNION ALL

    -- Recursive member: terminate if the previous level yielded a `NOT NULL` result
    SELECT p.ID, p.ParentID, p.CanRead, p.CanWrite, p.CanDelete
    FROM GET_PERMS AS gp 
    INNER JOIN Perms AS p ON gp.ParentID = p.ID    
    WHERE gp.CanRead IS NULL
)
SELECT CanRead, CanWrite, CanDelete 
FROM GET_PERMS
WHERE CanRead IS NOT NULL

当从数据库中检索到 Permission 记录时,RECURSIVE CTE 终止。

Demo here

WITH RECURSIVE tree AS (
        SELECT i.id AS my_id
                , p.id AS perm_id
        FROM items i
        JOIN permission p ON p.id = i.id
        WHERE i.id = 0 -- root
        UNION ALL
        SELECT l.id AS my_id
                , COALESCE (p.id,t.perm_id) AS perm_id
        FROM links l
        JOIN tree t ON l.lid = t.my_id
        LEFT JOIN permission p ON p.id = l.id
        )
SELECT i.*, t.perm_id
        , p.canread, p.canwrite, p.candelete
FROM items i
JOIN tree t ON t.my_id = i.id
JOIN permission p ON t.perm_id = p.id
        ;

结果:

CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 7
INSERT 0 6
INSERT 0 4
 id | name  |   descr   | perm_id | canread | canwrite | candelete 
----+-------+-----------+---------+---------+----------+-----------
  0 | Root  | Base Item |       0 | t       | t        | t
  1 | One   | First     |       0 | t       | t        | t
  2 | Two   | Second    |       2 | t       | f        | f
  3 | Three | Third     |       0 | t       | t        | t
  4 | Four  | Forth     |       2 | t       | f        | f
  5 | Five  | Fifth     |       5 | t       | t        | f
  6 | Six   | Sixth     |       6 | f       | f        | f
(7 rows)

仅供参考:这是向上的树状步道,不太优雅,IMO

WITH RECURSIVE tree_up AS (
        SELECT i.id AS my_id
                , p.id AS perm_id
        FROM items i
        LEFT JOIN permission p ON p.id = i.id
        WHERE i.id = 3 -- << PARAMETER: starting point
        UNION ALL
        SELECT l.lid AS my_id
                , COALESCE (t.perm_id,p.id) AS perm_id
        FROM tree_up t
        JOIN links l ON l.id = t.my_id
        LEFT JOIN permission p ON p.id = l.lid
        WHERE t.perm_id IS NULL
        )
SELECT i.*, t.perm_id
        , p.canread, p.canwrite, p.candelete
FROM items i
JOIN tree_up t ON t.my_id = i.id
JOIN permission p ON t.perm_id = p.id
        ;