mysql 递归自连接相交列
mysql recursive self join intersecting columns
假设我有一个像这样的起始 table(派生的)...
-------------------------------------------------------
| UsergroupID | ParentID | PermissionIDs |
-------------------------------------------------------
| 1 | 0 | 1 |
| 1 | 0 | 2 |
| 1 | 0 | 3 |
| 1 | 0 | 4 |
| 1 | 0 | 5 |
| 1 | 0 | 6 |
| 2 | 1 | 1 |
| 2 | 1 | 8 |
| 2 | 1 | 9 |
| 3 | 1 | 3 |
| 3 | 1 | 8 |
| 3 | 1 | 2 |
-------------------------------------------------------
我希望获得如下所示的最终结果集
-------------------------------------------------------
| UsergroupID | ParentID | PermissionID |
-------------------------------------------------------
| 1 | 0 | 1 |
| 1 | 0 | 2 |
| 1 | 0 | 3 |
| 1 | 0 | 4 |
| 1 | 0 | 5 |
| 1 | 0 | 6 |
| 2 | 1 | 1 |
| 3 | 1 | 3 |
| 3 | 1 | 2 |
-------------------------------------------------------
这基本上是对 parent id 进行递归查找,然后交叉(内部连接)PermissionID 列中的值。所以 child 永远不会比 parent 拥有更多的权限。
我已经查找了有关用户定义函数的内容(我想我可以将一个 udf 包裹在一个列周围并让它根据 parent id 递归地相交)但这并没有让我走得太远。我真正能想到的唯一一件事不是在数据库端做,而是用服务器端代码做。
Solarflare -- 这是我刚刚使用您的脚本尝试的...这有效!
delimiter $$
CREATE PROCEDURE prcPermCleanup5()
BEGIN
DROP TABLE IF EXISTS table1;
CREATE TABLE table1 (usergroupID INT, parentID INT, StoreID INT) ENGINE=MEMORY;
INSERT INTO table1 VALUES
(1,0,1),
(1,0,2),
(1,0,3),
(1,0,4),
(2,1,1),
(2,1,2),
(2,0,5),
(3,2,2),
(3,2,7),
(4,1,1),
(4,1,2),
(5,4,1),
(5,4,8),
(6,2,1),
(6,2,6);
REPEAT
DELETE entry.*
FROM table1 entry
LEFT JOIN table1 parent
ON entry.parentID = parent.usergroupID
AND entry.`StoreID` = parent.StoreID
WHERE parent.usergroupID IS NULL
AND NOT entry.parentID = 0;
UNTIL row_count() = 0 END REPEAT;
SELECT * FROM table1;
END $$
delimiter ;
不可能在单个查询中执行此操作(除非可能在某些特殊条件下),但您可以重复清理查询以这种方式进行递归。
如果您的清理工作是一次性的,您可以 运行 多次执行以下查询,直到不再有任何变化(您最多需要 depth of tree - 1
运行s):
delete entry.*
from table1 entry
left join table1 parent
on entry.parentID = parent.usergroupID
and entry.permissionIDs = parent.permissionIDs
where parent.usergroupID is null
and not entry.parentID = 0;
您可以在程序中自动执行该重复操作,例如
delimiter $$
create procedure prcPermCleanup()
begin
repeat
delete entry.*
from table1 entry
left join table1 parent
on entry.parentID = parent.usergroupID
and entry.permissionIDs = parent.permissionIDs
where parent.usergroupID is null
and not entry.parentID = 0;
until row_count() = 0 end repeat;
end $$
delimiter ;
call prcPermCleanup;
作为旁注:
您可能想要规范化您的数据,例如有一个单独的 table 用于您的权限:
table permissions: usergroupID | permissionID
table tree: usergroupID | parentID
在你当前的 table 中,你在 table 中有多次相同的信息(parentID
是 usergroupID
的父级的信息),又名非规范化。一个实际的结果是,对于同一个 usergroupID
,您可以有两个不同的 parentID
,这通常在树中是未定义的。
好的,所以这不一定是答案...但在以下设置中,我发现我得到了正确的 table 信息。这个例子在我知道的假设下工作(并且可以传递最大深度和分组关系(我 相信 我可以,尽管当我实际尝试这个时分组关系可能是一个问题"real"数据)。反正我有这个
DROP TABLE IF EXISTS joshTestTable;
CREATE TABLE joshTestTable (id INT, parent INT, store INT) ENGINE=MEMORY;
INSERT INTO joshTestTable VALUES
(1,0,1),
(1,0,2),
(1,0,3),
(1,0,4),
(2,1,1),
(2,1,2),
(2,0,5),
(3,2,2),
(3,2,7),
(4,1,1),
(4,1,2),
(5,4,1),
(5,4,8),
(6,2,1),
(6,2,6);
SELECT * FROM (
SELECT * FROM joshTestTable p
WHERE p.parent = 0
UNION ALL
SELECT c.* FROM joshTestTable p
LEFT JOIN joshTestTable c
ON p.id = c.parent
AND p.store = c.store
AND c.parent=1
UNION ALL
SELECT c.* FROM joshTestTable p
LEFT JOIN joshTestTable c
ON p.id = c.parent
AND p.store = c.store
AND c.parent IN (2,4) ) outter WHERE id IS NOT NULL;
作为解决方案,我仍然不是很热衷于此,因为我需要将所有已知的东西传递给存储过程来自动执行此操作。我只是通过对树深度进行左连接来获得真正的接近,但在那种情况下,我得到一个 table 和一堆列集,我需要以某种方式进行准透视。例如.
SELECT * FROM joshTestTable p
LEFT JOIN joshTestTable c
ON p.id = c.parent
AND p.store = c.store
LEFT JOIN joshTestTable gc
ON c.id = gc.parent
AND c.store = gc.store
WHERE p.parent = 0;
这最终为我提供了所有信息,但它的格式我认为我无法使用。它给了我这样的 table...
id parent store id parent store id parent store
1 0 1 2 1 1 6 2 1
1 0 1 4 1 1 5 4 1
1 0 2 2 1 2 3 2 2
1 0 2 4 1 2 NULL NULL NULL
1 0 3 NULL NULL NULL NULL NULL NULL
1 0 4 NULL NULL NULL NULL NULL NULL
2 0 5 NULL NULL NULL NULL NULL NULL
最后我正在寻找这个(这是我的第一个查询确实给我的)...
id parent store
1 0 1
1 0 2
1 0 3
1 0 4
2 0 5
2 1 1
2 1 2
3 2 2
4 1 1
4 1 2
5 4 1
6 2 1
假设我有一个像这样的起始 table(派生的)...
-------------------------------------------------------
| UsergroupID | ParentID | PermissionIDs |
-------------------------------------------------------
| 1 | 0 | 1 |
| 1 | 0 | 2 |
| 1 | 0 | 3 |
| 1 | 0 | 4 |
| 1 | 0 | 5 |
| 1 | 0 | 6 |
| 2 | 1 | 1 |
| 2 | 1 | 8 |
| 2 | 1 | 9 |
| 3 | 1 | 3 |
| 3 | 1 | 8 |
| 3 | 1 | 2 |
-------------------------------------------------------
我希望获得如下所示的最终结果集
-------------------------------------------------------
| UsergroupID | ParentID | PermissionID |
-------------------------------------------------------
| 1 | 0 | 1 |
| 1 | 0 | 2 |
| 1 | 0 | 3 |
| 1 | 0 | 4 |
| 1 | 0 | 5 |
| 1 | 0 | 6 |
| 2 | 1 | 1 |
| 3 | 1 | 3 |
| 3 | 1 | 2 |
-------------------------------------------------------
这基本上是对 parent id 进行递归查找,然后交叉(内部连接)PermissionID 列中的值。所以 child 永远不会比 parent 拥有更多的权限。
我已经查找了有关用户定义函数的内容(我想我可以将一个 udf 包裹在一个列周围并让它根据 parent id 递归地相交)但这并没有让我走得太远。我真正能想到的唯一一件事不是在数据库端做,而是用服务器端代码做。
Solarflare -- 这是我刚刚使用您的脚本尝试的...这有效!
delimiter $$
CREATE PROCEDURE prcPermCleanup5()
BEGIN
DROP TABLE IF EXISTS table1;
CREATE TABLE table1 (usergroupID INT, parentID INT, StoreID INT) ENGINE=MEMORY;
INSERT INTO table1 VALUES
(1,0,1),
(1,0,2),
(1,0,3),
(1,0,4),
(2,1,1),
(2,1,2),
(2,0,5),
(3,2,2),
(3,2,7),
(4,1,1),
(4,1,2),
(5,4,1),
(5,4,8),
(6,2,1),
(6,2,6);
REPEAT
DELETE entry.*
FROM table1 entry
LEFT JOIN table1 parent
ON entry.parentID = parent.usergroupID
AND entry.`StoreID` = parent.StoreID
WHERE parent.usergroupID IS NULL
AND NOT entry.parentID = 0;
UNTIL row_count() = 0 END REPEAT;
SELECT * FROM table1;
END $$
delimiter ;
不可能在单个查询中执行此操作(除非可能在某些特殊条件下),但您可以重复清理查询以这种方式进行递归。
如果您的清理工作是一次性的,您可以 运行 多次执行以下查询,直到不再有任何变化(您最多需要 depth of tree - 1
运行s):
delete entry.*
from table1 entry
left join table1 parent
on entry.parentID = parent.usergroupID
and entry.permissionIDs = parent.permissionIDs
where parent.usergroupID is null
and not entry.parentID = 0;
您可以在程序中自动执行该重复操作,例如
delimiter $$
create procedure prcPermCleanup()
begin
repeat
delete entry.*
from table1 entry
left join table1 parent
on entry.parentID = parent.usergroupID
and entry.permissionIDs = parent.permissionIDs
where parent.usergroupID is null
and not entry.parentID = 0;
until row_count() = 0 end repeat;
end $$
delimiter ;
call prcPermCleanup;
作为旁注:
您可能想要规范化您的数据,例如有一个单独的 table 用于您的权限:
table permissions: usergroupID | permissionID
table tree: usergroupID | parentID
在你当前的 table 中,你在 table 中有多次相同的信息(parentID
是 usergroupID
的父级的信息),又名非规范化。一个实际的结果是,对于同一个 usergroupID
,您可以有两个不同的 parentID
,这通常在树中是未定义的。
好的,所以这不一定是答案...但在以下设置中,我发现我得到了正确的 table 信息。这个例子在我知道的假设下工作(并且可以传递最大深度和分组关系(我 相信 我可以,尽管当我实际尝试这个时分组关系可能是一个问题"real"数据)。反正我有这个
DROP TABLE IF EXISTS joshTestTable;
CREATE TABLE joshTestTable (id INT, parent INT, store INT) ENGINE=MEMORY;
INSERT INTO joshTestTable VALUES
(1,0,1),
(1,0,2),
(1,0,3),
(1,0,4),
(2,1,1),
(2,1,2),
(2,0,5),
(3,2,2),
(3,2,7),
(4,1,1),
(4,1,2),
(5,4,1),
(5,4,8),
(6,2,1),
(6,2,6);
SELECT * FROM (
SELECT * FROM joshTestTable p
WHERE p.parent = 0
UNION ALL
SELECT c.* FROM joshTestTable p
LEFT JOIN joshTestTable c
ON p.id = c.parent
AND p.store = c.store
AND c.parent=1
UNION ALL
SELECT c.* FROM joshTestTable p
LEFT JOIN joshTestTable c
ON p.id = c.parent
AND p.store = c.store
AND c.parent IN (2,4) ) outter WHERE id IS NOT NULL;
作为解决方案,我仍然不是很热衷于此,因为我需要将所有已知的东西传递给存储过程来自动执行此操作。我只是通过对树深度进行左连接来获得真正的接近,但在那种情况下,我得到一个 table 和一堆列集,我需要以某种方式进行准透视。例如.
SELECT * FROM joshTestTable p
LEFT JOIN joshTestTable c
ON p.id = c.parent
AND p.store = c.store
LEFT JOIN joshTestTable gc
ON c.id = gc.parent
AND c.store = gc.store
WHERE p.parent = 0;
这最终为我提供了所有信息,但它的格式我认为我无法使用。它给了我这样的 table...
id parent store id parent store id parent store
1 0 1 2 1 1 6 2 1
1 0 1 4 1 1 5 4 1
1 0 2 2 1 2 3 2 2
1 0 2 4 1 2 NULL NULL NULL
1 0 3 NULL NULL NULL NULL NULL NULL
1 0 4 NULL NULL NULL NULL NULL NULL
2 0 5 NULL NULL NULL NULL NULL NULL
最后我正在寻找这个(这是我的第一个查询确实给我的)...
id parent store
1 0 1
1 0 2
1 0 3
1 0 4
2 0 5
2 1 1
2 1 2
3 2 2
4 1 1
4 1 2
5 4 1
6 2 1