EXISTS 中的递归 CTE 查询
Recursive CTE query in EXISTS
我 运行 遇到 mariaDB/mysql 上的递归 cte 问题。设置并不太复杂:有一个 users
table、一个 roles
table 和一个为用户分配角色的 user_roles
table .但是,角色可以嵌套,roles
table 包含一个 parent_id
字段来完成此操作。 parent_id
字段在它是根角色时将为 NULL,如果不是,则包含另一个角色的 ID。
这可以通过以下创建和插入进行设置:
CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
PRIMARY KEY (id)
);
CREATE TABLE roles (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
parent_id INT,
PRIMARY KEY (id)
);
CREATE TABLE user_roles (
id INT NOT NULL AUTO_INCREMENT,
user_id INT,
role_id INT,
PRIMARY KEY (id)
);
INSERT INTO users(name) VALUES ('Alice'),('Bob'),('Charlie');
INSERT INTO roles(name, parent_id) VALUES
('superuser',null),
('admin',1),
('ceo',1),
('employee', null);
INSERT INTO user_roles(user_id, role_id) VALUES
(1,2),
(2,3),
(3,4);
我的目标是创建一个查询,选择其中一个用户的角色(或其祖先之一)与给定角色名称匹配的所有用户。我想到了这个:
SELECT * FROM users
WHERE EXISTS(
SELECT 1 FROM roles
INNER JOIN user_roles ON roles.id = user_roles.role_id
WHERE users.id = user_roles.user_id
AND EXISTS(
WITH RECURSIVE cte AS (
SELECT * FROM roles as roles_recursive
WHERE roles_recursive.id = roles.id
UNION ALL
SELECT roles_recursive.* FROM roles as roles_recursive
INNER JOIN cte ON cte.parent_id = roles_recursive.id
)
SELECT 1 from cte WHERE name='superuser'
)
);
我设置了 DB fiddle 来重现我的问题。请查看那边的完整复制案例。
这会在不同的引擎上产生不同的结果:
- MySQL 8.0.18:我目前的开发机器只运行macOS 10.13,所以我仅限于这个MySQL版本。在这个版本中,我只得到 'Alice' 作为结果,而 'Bob' 也应该在结果中。
- DB fiddle,MariaDB 10.3.30:产生错误:“ER_BAD_FIELD_ERROR:'where clause' 中的未知列 'roles.id'”。角色范围似乎在 CTE 中不可用。
- dbfiddle.uk 似乎是 运行 MySQL 的另一个版本并且 return 是正确的结果集。
有没有至少在 MariaDB 中有效的方法?我不是在寻找简单连接的解决方案,角色应该支持可变深度。
这是一个例子。
all_roles
递归查找所有与其完整祖先配对的角色。
all_roles.base_role
是起始子角色 id
,base_name
是起始名称。
uRoles
找到与每个 base_role
.
关联的相应 user_id
最后,我们找到所有具有与给定角色名称匹配的 uRoles 行的用户。
注意:为了以防万一,我添加了等级限制。如果您愿意,可以删除它,或者根据需要增加它,以避免可能导致无限递归的问题。
WITH RECURSIVE all_roles (base_role, base_name, id, name, parent_id, lev) AS (
SELECT r.id AS base_role, r.name , r.*, 1 AS lev
FROM roles AS r
UNION ALL
SELECT r0.base_role , r0.base_name, r.*, lev + 1
FROM roles AS r
JOIN all_roles AS r0
ON r.id = r0.parent_id
AND lev < 8
)
, uRoles AS (
SELECT ur.*, ar.name, ar.base_name, ar.lev
FROM all_roles AS ar
JOIN user_roles AS ur
ON ar.base_role = ur.role_id
)
SELECT *
FROM users
WHERE id IN (SELECT user_id FROM uRoles WHERE name = 'superuser')
;
Result:
+----+-------+
| id | name |
+----+-------+
| 1 | Alice |
| 2 | Bob |
+----+-------+
首先找到给定角色层次结构中的所有角色,然后 select 用户担任找到的任何角色。
WITH RECURSIVE cte AS (
-- The role and its descendants
SELECT *
FROM roles
WHERE name = 'superuser'
UNION ALL
SELECT roles.*
FROM roles
JOIN cte ON cte.id = roles.parent_id
)
SELECT DISTINCT u.*
FROM users u
JOIN user_roles ur ON u.id = ur.user_id
JOIN cte r ON r.id = ur.role_id
我 运行 遇到 mariaDB/mysql 上的递归 cte 问题。设置并不太复杂:有一个 users
table、一个 roles
table 和一个为用户分配角色的 user_roles
table .但是,角色可以嵌套,roles
table 包含一个 parent_id
字段来完成此操作。 parent_id
字段在它是根角色时将为 NULL,如果不是,则包含另一个角色的 ID。
这可以通过以下创建和插入进行设置:
CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
PRIMARY KEY (id)
);
CREATE TABLE roles (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
parent_id INT,
PRIMARY KEY (id)
);
CREATE TABLE user_roles (
id INT NOT NULL AUTO_INCREMENT,
user_id INT,
role_id INT,
PRIMARY KEY (id)
);
INSERT INTO users(name) VALUES ('Alice'),('Bob'),('Charlie');
INSERT INTO roles(name, parent_id) VALUES
('superuser',null),
('admin',1),
('ceo',1),
('employee', null);
INSERT INTO user_roles(user_id, role_id) VALUES
(1,2),
(2,3),
(3,4);
我的目标是创建一个查询,选择其中一个用户的角色(或其祖先之一)与给定角色名称匹配的所有用户。我想到了这个:
SELECT * FROM users
WHERE EXISTS(
SELECT 1 FROM roles
INNER JOIN user_roles ON roles.id = user_roles.role_id
WHERE users.id = user_roles.user_id
AND EXISTS(
WITH RECURSIVE cte AS (
SELECT * FROM roles as roles_recursive
WHERE roles_recursive.id = roles.id
UNION ALL
SELECT roles_recursive.* FROM roles as roles_recursive
INNER JOIN cte ON cte.parent_id = roles_recursive.id
)
SELECT 1 from cte WHERE name='superuser'
)
);
我设置了 DB fiddle 来重现我的问题。请查看那边的完整复制案例。 这会在不同的引擎上产生不同的结果:
- MySQL 8.0.18:我目前的开发机器只运行macOS 10.13,所以我仅限于这个MySQL版本。在这个版本中,我只得到 'Alice' 作为结果,而 'Bob' 也应该在结果中。
- DB fiddle,MariaDB 10.3.30:产生错误:“ER_BAD_FIELD_ERROR:'where clause' 中的未知列 'roles.id'”。角色范围似乎在 CTE 中不可用。
- dbfiddle.uk 似乎是 运行 MySQL 的另一个版本并且 return 是正确的结果集。
有没有至少在 MariaDB 中有效的方法?我不是在寻找简单连接的解决方案,角色应该支持可变深度。
这是一个例子。
all_roles
递归查找所有与其完整祖先配对的角色。
all_roles.base_role
是起始子角色 id
,base_name
是起始名称。
uRoles
找到与每个 base_role
.
user_id
最后,我们找到所有具有与给定角色名称匹配的 uRoles 行的用户。
注意:为了以防万一,我添加了等级限制。如果您愿意,可以删除它,或者根据需要增加它,以避免可能导致无限递归的问题。
WITH RECURSIVE all_roles (base_role, base_name, id, name, parent_id, lev) AS (
SELECT r.id AS base_role, r.name , r.*, 1 AS lev
FROM roles AS r
UNION ALL
SELECT r0.base_role , r0.base_name, r.*, lev + 1
FROM roles AS r
JOIN all_roles AS r0
ON r.id = r0.parent_id
AND lev < 8
)
, uRoles AS (
SELECT ur.*, ar.name, ar.base_name, ar.lev
FROM all_roles AS ar
JOIN user_roles AS ur
ON ar.base_role = ur.role_id
)
SELECT *
FROM users
WHERE id IN (SELECT user_id FROM uRoles WHERE name = 'superuser')
;
Result:
+----+-------+
| id | name |
+----+-------+
| 1 | Alice |
| 2 | Bob |
+----+-------+
首先找到给定角色层次结构中的所有角色,然后 select 用户担任找到的任何角色。
WITH RECURSIVE cte AS (
-- The role and its descendants
SELECT *
FROM roles
WHERE name = 'superuser'
UNION ALL
SELECT roles.*
FROM roles
JOIN cte ON cte.id = roles.parent_id
)
SELECT DISTINCT u.*
FROM users u
JOIN user_roles ur ON u.id = ur.user_id
JOIN cte r ON r.id = ur.role_id