从其他 table 中查找具有组值的行

Find rows with a group values from other table

id role Group_ID
1 A 1
2 B 1
3 A 2
4 D 2
5 A 3
6 B 3
7 C 3
8 C 4

...

User_id role
user1 A
user1 B
user2 C
user2 D
user3 A
user3 D
user4 C
user5 A
user5 B
user5 C
user5 D

...

我有 2 个表 Table1 和 Table2,如上所示。 我的要求是从具有组中所有角色的 table2 中获取 User_ID。此外,只需要检查至少具有 2 个角色的那些组。如果 group_ID 只有 1 个角色,则不应考虑

例如,以上 2 个表格的结果如下所示

用户 1 具有组 1 (A,B) 中的两个角色 -> 因此它在结果中。

user3 具有组 2 (A,D) 中的两个角色 -> 因此它在结果中。

user5 具有组 1(A,B)、2(A,D) 和 3(A,B,C) 中的所有角色 -> 因此它在结果中。

用户 2 的角色 C 和 D 不是一个组,因此不会显示在结果中

User4 的角色 C 是一个组 (Group_ID = 4),但该组至少应有 2 个角色,因此不会显示在结果中

User_id Group_ID
user1 1
user3 2
user5 1
user5 2
user5 3

.....

Select Table2.USER_ID,Table1.GROUP_ID 
from Table2, 
     Table1 
Where Table2.ROLE =  Table1.ROLE
group by Table1.GROUP_ID,Table2.USER_ID

通过上面的查询,我可以获取分配了 user_id 任何角色的记录。但是,我想从具有组中所有角色的 table2 中获取 User_ID。

非常感谢任何帮助。我也会确保接受引导我找到解决方案的答案

这是一个适用于 Oracle 数据库的解决方案;为 SQL 服务器调整它(如果可能的话;我不知道那种方言)。

测试数据(其他人可能也用这个):

create table table1(id number, role varchar2(10), group_id number);

insert into table1 (id, role, group_id)
    select 1, 'A', 1 from dual union all
    select 2, 'B', 1 from dual union all
    select 3, 'A', 2 from dual union all
    select 4, 'D', 2 from dual union all
    select 5, 'A', 3 from dual union all
    select 6, 'B', 3 from dual union all
    select 7, 'C', 3 from dual
;

create table table2 (user_id varchar2(20), role varchar2(10));

insert into table2 (user_id, role)
    select 'user1', 'A' from dual union all
    select 'user1', 'B' from dual union all
    select 'user2', 'C' from dual union all
    select 'user2', 'D' from dual union all
    select 'user3', 'A' from dual union all
    select 'user3', 'D' from dual union all
    select 'user4', 'C' from dual union all
    select 'user5', 'A' from dual union all
    select 'user5', 'B' from dual union all
    select 'user5', 'C' from dual union all
    select 'user5', 'D' from dual
;

commit;

创建user-defined数据类型(代表角色的字符串集合):

create or replace type role_list as table of varchar2(10);
/

查询和输出:

select a2.user_id, a1.group_id
from   (
         select user_id, cast(collect(role) as role_list) as user_roles
         from   table2
         group  by user_id
       ) a2
       inner join
       (
         select group_id, cast(collect(role) as role_list) as group_roles
         from   table1
         group  by group_id
       ) a1
       on a1.group_roles submultiset of a2.user_roles
order  by user_id, group_id
;

USER_ID         GROUP_ID
------------  ----------
user1                  1
user3                  2
user5                  1
user5                  2
user5                  3

该策略非常明显,应该很容易直接从代码中阅读。在第一个 table 中按 group_id 对角色进行分组,在第二个 user_id 中按 user_id 对角色进行分组。识别在用户的角色列表中找到组的所有角色的所有对(用户、组)——这正是 submultiset 比较运算符所做的。

一个更基本的查询(更难遵循和维护,并且可能更慢),但可能有帮助,因为它可能只需要很少的更改 - 如果有的话 - 在几乎所有 SQL 方言中,可能看起来像这样。假设 role 不能是 null in table2 (为了让查询稍微简单一点):

select q2.user_id, q1.group_id
from   (select distinct user_id  from table2) q2
       cross join
       (select distinct group_id from table1) q1
where  not exists 
       (
         select role
         from   table1
         where  group_id = q1.group_id
           and  role not in
                     (
                       select role
                       from   table2
                       where  user_id = q2.user_id
                     )
       )
order  by user_id, group_id
;

直接将需求翻译成 SQL(Oracle 风格)

with g as (
select group_id, count(*) role_cnt from table1 group by group_id having count(*) > 1),
u as (
select u.user_id, g.group_id, count(*) usergroup_role_cnt from table1 g, table2 u
where u.role=g.role
group by u.user_id, g.group_id)
select u.user_id, g.group_id from g, u
where u.group_id=g.group_id
and u.usergroup_role_cnt=g.role_cnt;

编辑:

有人担心当表“非常大”时使用“分组依据”的查询性能。我不知道“非常大”是如何定义的。不管怎样,我在 Oracle Clould Free Tier ATP 数据库 19c 上做了一个测试,使用了数万行的表。这是结果。

select count(*) from table1;

COUNT(*) 
-------- 
  289327 


Elapsed: 00:00:00.007
1 rows selected.


select count(*) from table2;

COUNT(*) 
-------- 
 1445328 


Elapsed: 00:00:00.024
1 rows selected.


with g as (
select group_id, count(*) role_cnt from table1 group by group_id having count(*) > 1),
u as (
select u.user_id, g.group_id, count(*) usergroup_role_cnt from table1 g, table2 u
where u.role=g.role
group by u.user_id, g.group_id)
select u.user_id, g.group_id from g, u
where u.group_id=g.group_id
and u.usergroup_role_cnt=g.role_cnt;


USER_ID GROUP_ID 
------- -------- 
user99       994 
user97       462 
user97       199 
user87        35 
user99       462 
user87       179 
user99       199 
user98       199 
user96       199 
user87       813 
user87       941 
user96       994 
user97       994 
user96       462 
user98       462 
user98       994 


Elapsed: 00:00:04.770
16 rows selected.

select a2.user_id, a1.group_id
from   (
         select user_id, cast(collect(role) as role_list) as user_roles
         from   table2
         group  by user_id
       ) a2
       inner join
       (
         select group_id, cast(collect(role) as role_list) as group_roles
         from   table1
         group  by group_id
       ) a1
       on a1.group_roles submultiset of a2.user_roles
order  by user_id, group_id
;

USER_ID GROUP_ID 
------- -------- 
user87        35 
user87       179 
user87       813 
user87       941 
user96       199 
user96       462 
user96       994 
user97       199 
user97       462 
user97       994 
user98       199 
user98       462 
user98       994 
user99       199 
user99       462 
user99       994 


Elapsed: 00:01:35.395
16 rows selected.

select q2.user_id, q1.group_id
from   (select distinct user_id  from table2) q2
       cross join
       (select distinct group_id from table1) q1
where  not exists 
       (
         select role
         from   table1
         where  group_id = q1.group_id
           and  role not in
                     (
                       select role
                       from   table2
                       where  user_id = q2.user_id
                     )
       )
order  by user_id, group_id
;

(Manually cancelled after 10 min)

数据是随机生成的,有些偏差。但在现实中,倾斜的数据是不可避免的。统计数据应尽量减少影响。 (两个表都分析过,没有索引)