我在 mariaDB/mysql 上遇到了递归 cte 的问题。设置并不太复杂:有一个 users
表、一个 roles
表和一个为用户分配角色的 user_roles
表。但是,角色可以嵌套,并且 roles
表包含一个 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 子句’中的未知列‘roles.id’”。看起来角色范围在 CTE 中不可用。
- dbfiddle.uk似乎正在运行另一个版本的 MySQL 并且确实返回了正确的结果集。
有什么方法可以做到这一点,至少在 MariaDB 中有效吗?我不是在寻找具有简单连接的解决方案,角色应该支持可变深度。
最佳答案
首先从给定的角色中查找层次结构中的所有角色,然后选择担任找到的任何角色的用户。
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
关于mysql - EXISTS 中的递归 CTE 查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69337198/