【问题标题】:Getting distinct rows from a inner join从内部连接中获取不同的行
【发布时间】:2019-11-19 14:16:43
【问题描述】:

我有以下五个表,用于管理数据库中用户、组和角色之间的关系:

  • 用户
  • 用户组
  • group_role
  • 角色

一个用户属于一个组。此数据存储在 user_group 表中,该表只有两列:user_fk 和 group_fk。 一个组可以包含许多角色。此信息存储在表 group_role 中,该表只有两列:group_fk 和 role_fk。

所以我对这 5 个表编写了以下查询,连接结果为我自己提供了例如 5 行,因为我所属的组包含 5 个角色。

SELECT
user.last_name,
user.first_name,
user.user_name,
group.name,
role.name 
FROM
user 
INNER JOIN user_group ON user_group.user_fk = user.id
INNER JOIN group ON group.id = user_group.group_fk
INNER JOIN group_role ON group.id = group_role.group_fk
INNER JOIN role ON group_role.role_fk = role.id

有没有办法将查询更改为例如只为一个用户返回一行并列出他的所有组和角色?

【问题讨论】:

  • 你知道你想在这一行显示哪些角色吗?

标签: sql oracle join


【解决方案1】:

简单地聚合每个用户并使用LISTAGG 来获取他们的组和角色字符串。为此,您需要 Oracle 19c,因为 Oracle 在早期版本中忘记为 LISTAGG 实现 DISTINCT

SELECT
  user.last_name,
  user.first_name,
  user.user_name,
  LISTAGG(group.name, ', ') WITHIN GROUP (ORDER BY group.name) AS groups,
  LISTAGG(DISTINCT role.name, ', ') WITHIN GROUP (ORDER BY role.name) AS roles
FROM user 
INNER JOIN user_group ON user_group.user_fk = user.id
INNER JOIN group ON group.id = user_group.group_fk
INNER JOIN group_role ON group.id = group_role.group_fk
INNER JOIN role ON group_role.role_fk = role.id
GROUP BY user.last_name, user.first_name, user.user_name
ORDER BY user.last_name, user.first_name, user.user_name;

在早期版本中,您可以使用REGEXP_REPLACE 删除由LISTAGG 生成的重复项,前提是列表的长度不超过LISTAGG 可以处理的长度。

REGEXP_REPLACE(
  LISTAGG(role.name, ', ') WITHIN GROUP (ORDER BY role.name),
  '([^, ]+)(, \1)+($|, )',
  '\1\3')

【讨论】:

  • 适用于 REGEXP_REPLACE
  • @Thorsten Kettner ,有没有办法更改正则表达式,以便也可以匹配由多个单词组成的数据条目?该表达式查找并聚合“groupOne”,但不为“第二组”执行此操作
  • 那是因为分隔符包含一个空格。最简单的解决方案是使用唯一的逗号作为分隔符 (REGEXP_REPLACE(LISTAGG(role.name,',') WITHIN GROUP (ORDER BY role.name),'([^,]+)(,\1)+($|,)','\1\3'))。
  • 它有效!即使我不懂这个复杂的正则表达式。
  • 括号的作用类似于变量。第一对括号内的内容(这里是[^,]+)可以通过\1访问,第二对括号的内容可以通过\2等访问。表达式如下:一系列非逗号字符([^,]+) 后跟一个或多次逗号和同一系列 (,\1)+,最后跟另一个逗号或根本没有,即它是字符串 ($|,) 的结尾。每一次重复出现的字符序列都将被替换为单个出现 \1 后跟逗号,或者在字符串结尾的情况下不替换 \3
【解决方案2】:

您可以将listagg 聚合函数与group by 一起使用,如下所示:

SELECT user.last_name,
       user.first_name, 
       user.user_name, 
       listagg(group.name,',') within group (order by group.name) as groupss,
       listagg(role.name,',') within group (order by role.name) as roless 
FROM user  INNER JOIN user_group ON user_group.user_fk = user.id 
INNER JOIN group ON group.id = user_group.group_fk 
INNER JOIN group_role ON group.id = group_role.group_fk 
INNER JOIN role ON group_role.role_fk = role.id 
Group by  user.last_name, 
          user.first_name, 
          user.user_name;

【讨论】:

    【解决方案3】:

    我会推荐两个子查询。为什么?角色可能会重复,listagg() 不支持distinct

    所以:

    select u.last_name, u.first_name, u.user_name, 
           ug.groups, ur.roles           
    from user u left join
         (select ug.user_fk,
                 listagg(g.name, ',') within group (order by g.name) as groups
          from user_group ug join
               group g
               on g.id = ug.group_fk 
          group by ug.user_fk
         ) ug
         on ug.user_fk = u.id left join
         (select ug.user_fk,
                 listagg(r.name, ',') within group (order by r.name) as roles 
          from user_group ug join
               group g join
               on g.id = ug.group_fk join
               group_role gr
               on g.id = gr.group_fk join
               role r
               on gr.role_fk = r.id 
          group by ug.user_fk
         ) ur
         on ur.user_fk = u.id;
    

    您也可以使用一个子查询和条件聚合来编写:

    select u.last_name, u.first_name, u.user_name, 
           listagg(case when seqnum_g = 1 then ugr.name end, ',') within group (order by ug.name) as groups,
           listagg(case when seqnum_r = 1 then ugr.name end, ',') as roles within group (order by ur.name) as roles 
    from user u left join
         (select ug.user_fk,
                 row_number() over (partition by ug.user_fk order by g.name) as seqnum_g,
                 row_number() over (partition by ug.user_rk order by r.name) as seqnum_r
          from user_group ug join
               group g join
               on g.id = ug.group_fk join
               group_role gr
               on g.id = gr.group_fk join
               role r
               on gr.role_fk = r.id 
         ) ugr
         on ugr.user_fk = u.id
    group by u.last_name, u.first_name, u.user_name; 
    

    【讨论】:

    • 嗨,结果是:“[42000]: ORA-00903: 无效的表名”。没有 user_role 表和 user_role.user_fk 字段。
    • @user615993 。 . .也许我遗漏了一些东西,但我没有在这段代码中使用user_role
    • 我更正了,这是我的错字。然而 00904. 00000 - "%s: invalid identifier" - 返回第 3 行的错误:'order by r.name'。在我看来,在嵌套查询之外无法访问表 'r'
    • @user615993 。 . .我想我修复了别名。
    【解决方案4】:

    试试这样的:

    SELECT
    user.last_name,
    user.first_name,
    user.user_name,
    listagg(group.name || ' - ' ||role.name, ', ') within group (order by group.name, role.name)
    FROM user 
    INNER JOIN user_group ON user_group.user_fk = user.id
    INNER JOIN group ON group.id = user_group.group_fk
    INNER JOIN group_role ON group.id = group_role.group_fk
    INNER JOIN role ON group_role.role_fk = role.id
    group by user.last_name,
    user.first_name,
    user.user_name
    

    你需要两件事:

    1. 对您的结果进行分组,它是按子句分组完成的
    2. 连接每个组的角色,由 listagg 函数完成

    【讨论】:

      猜你喜欢
      • 2010-10-21
      • 1970-01-01
      • 1970-01-01
      • 2014-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-29
      • 1970-01-01
      相关资源
      最近更新 更多