mysql - EXISTS 中的递归 CTE 查询

标签 mysql sql mariadb common-table-expression

我在 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

db<>fiddle

关于mysql - EXISTS 中的递归 CTE 查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69337198/

相关文章:

mysql - sql join 返回两个相同的值

mysql NULL value in where in CLAUSE

mysql - 如何在 MySQL 中返回数据透视表输出?

php - 在没有 group_concat 的情况下连接 symfony/doctrine/mysql 中的行

mysql - 为什么 MariaDB 不接受我的字符串变量作为我的 sql 查询中的表名?

php - 如何记录手动付款的发票的概念或方法

sql - 在合并语句中使用连接

sql 根据条件选择最小值或最大值第 2 部分

mysql - 对于支持事务的 Node.js 和 MariaDB,我应该使用哪种 ORM?

python - Django ORM 查询无法选择新对象