PostgreSQL: Find permission for element, traverse up to root

瘦欲@ 提交于 2021-01-29 01:50:04

问题


Here is the DB Structure I'm using

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

Question is, if I want the permission for 6, I can directly query Permission table and get the Read/Write/Delete value. However if I want the permission for 4, it is not present in permission table, so I need to find the parent, which is 2, since I have the permission for 2 I can return it.

More tricky, If I want the permission for 3, I check in permission table, it is not present, see for the parent (1), which is not present, go for it's parent which is (0-Root), and return the value.

This can be for any level, imagine we do not have records 2, 5, 6 in permission table, so when I lookup for 6, then I need to traverse all the way up to root to get the permission.

Note: We always have the permission present for Root.

I want this to be done in DB layer than application layer, so any help in writing SQL query (recursive) or Stored Procedure would be great.

Thanks!!


回答1:


You can use a RECURSIVE CTE for this:

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

The RECURSIVE CTE terminates when a Permission record has been retrieved from the database.

Demo here




回答2:


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
        ;

Results:

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)

FYI: here is the treewalk up, which is less elegant, 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
        ;


来源:https://stackoverflow.com/questions/38121014/postgresql-find-permission-for-element-traverse-up-to-root

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!