You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多对多关系下用户角色权限聚合SQL查询问题

多对多权限聚合查询解决方案

核心思路

要实现用户权限的聚合(直接权限+角色继承权限),需分步骤预计算各关联数据,再合并去重:

  1. 预计算每个角色对应的权限集合,避免重复关联查询
  2. 计算每个用户的角色列表及各角色的权限集合
  3. 单独提取用户的直接权限集合
  4. 合并用户直接权限与角色继承权限,去重后得到最终权限数组

错误原因分析

你遇到的column "p_role_id" specified in USING clause does not exist in left table错误,是因为USING子句要求关联的两张表必须存在同名的关联列。比如你可能错误地将role_permission表的role_id写成了p_role_id,或者左表(如user_role)没有这个列。解决时优先用ON子句明确关联条件,避免依赖列名匹配。

PostgreSQL 实现示例

WITH role_perms AS (
    -- 预计算每个角色的权限数组
    SELECT 
        role_id,
        array_agg(DISTINCT permission_name) AS role_permissions
    FROM role_permission rp
    JOIN permission p ON rp.permission_id = p.permission_id
    GROUP BY role_id
),
user_roles_data AS (
    -- 计算用户的角色列表及对应角色的权限数组
    SELECT 
        ur.user_id,
        array_agg(DISTINCT r.role_name) AS user_role_array,
        array_agg(rp.role_permissions) AS roles_permissions_array
    FROM user_role ur
    JOIN role r ON ur.role_id = r.role_id
    JOIN role_perms rp ON ur.role_id = rp.role_id
    GROUP BY ur.user_id
),
user_direct_perms AS (
    -- 提取用户直接拥有的权限数组
    SELECT 
        up.user_id,
        array_agg(DISTINCT p.permission_name) AS direct_permissions
    FROM user_permission up
    JOIN permission p ON up.permission_id = p.permission_id
    GROUP BY up.user_id
)
SELECT 
    u.user_id,
    u.user_name,
    u.user_email,
    -- 处理无角色的用户,返回空数组
    COALESCE(urd.user_role_array, '{}'::text[]) AS user_role_array,
    -- 合并直接权限与角色权限并去重
    ARRAY(
        SELECT DISTINCT unnest(
            COALESCE(urd.role_permissions_flat, '{}'::text[]) || COALESCE(udp.direct_permissions, '{}'::text[])
        )
    ) AS user_permissions_array,
    COALESCE(urd.roles_permissions_array, '{}'::text[]) AS roles_permissions_array
FROM "user" u
LEFT JOIN (
    -- 将角色的多维权限数组展开为一维,方便合并
    SELECT 
        user_id,
        user_role_array,
        roles_permissions_array,
        array_agg(unnest(roles_permissions_array)) AS role_permissions_flat
    FROM user_roles_data
    GROUP BY user_id, user_role_array, roles_permissions_array
) urd ON u.user_id = urd.user_id
LEFT JOIN user_direct_perms udp ON u.user_id = udp.user_id
GROUP BY u.user_id, u.user_name, u.user_email, urd.user_role_array, urd.roles_permissions_array, urd.role_permissions_flat, udp.direct_permissions;

MySQL 实现示例

MySQL无原生数组类型,用字符串模拟集合:

WITH role_perms AS (
    SELECT 
        role_id,
        GROUP_CONCAT(DISTINCT permission_name SEPARATOR ',') AS role_permissions
    FROM role_permission rp
    JOIN permission p ON rp.permission_id = p.permission_id
    GROUP BY role_id
),
user_roles_data AS (
    SELECT 
        ur.user_id,
        GROUP_CONCAT(DISTINCT r.role_name SEPARATOR ',') AS user_role_array,
        GROUP_CONCAT(rp.role_permissions SEPARATOR ';') AS roles_permissions_array
    FROM user_role ur
    JOIN role r ON ur.role_id = r.role_id
    JOIN role_perms rp ON ur.role_id = rp.role_id
    GROUP BY ur.user_id
),
user_direct_perms AS (
    SELECT 
        up.user_id,
        GROUP_CONCAT(DISTINCT p.permission_name SEPARATOR ',') AS direct_permissions
    FROM user_permission up
    JOIN permission p ON up.permission_id = p.permission_id
    GROUP BY up.user_id
)
SELECT 
    u.user_id,
    u.user_name,
    u.user_email,
    COALESCE(urd.user_role_array, '') AS user_role_array,
    -- 合并去重:通过拆分字符串后去重再拼接
    (SELECT GROUP_CONCAT(DISTINCT val SEPARATOR ',') FROM (
        -- 拆分角色权限字符串
        SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(COALESCE(urd.roles_permissions_array, ''), ';'), ';', n.n), ';', -1)) AS val
        FROM (SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) n
        WHERE n.n <= LENGTH(CONCAT(COALESCE(urd.roles_permissions_array, ''), ';')) - LENGTH(REPLACE(CONCAT(COALESCE(urd.roles_permissions_array, ''), ';'), ';', ''))
        UNION
        -- 拆分直接权限字符串
        SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(COALESCE(udp.direct_permissions, ''), ',', n.n), ',', -1)) AS val
        FROM (SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) n
        WHERE n.n <= LENGTH(COALESCE(udp.direct_permissions, '')) - LENGTH(REPLACE(COALESCE(udp.direct_permissions, ''), ',', '')) + 1
    ) t WHERE val != '') AS user_permissions_array,
    COALESCE(urd.roles_permissions_array, '') AS roles_permissions_array
FROM user u
LEFT JOIN user_roles_data urd ON u.user_id = urd.user_id
LEFT JOIN user_direct_perms udp ON u.user_id = udp.user_id;

注意事项

  • MySQL中拆分字符串时,n的取值范围需根据系统中最大权限数调整,也可用递归CTE生成连续数字
  • 其他数据库(如SQL Server)可替换为对应聚合函数(如STRING_AGG)和数组处理函数

内容的提问来源于stack exchange,提问作者wdc92

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 07:19:57