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 来重现我的问题。请查看那边的完整复制案例。 这会在不同的引擎上产生不同的结果:

有没有至少在 MariaDB 中有效的方法?我不是在寻找简单连接的解决方案,角色应该支持可变深度。

这是一个例子。

all_roles 递归查找所有与其完整祖先配对的角色。

all_roles.base_role 是起始子角色 idbase_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

db<>fiddle