问题
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