【问题标题】:Oracle SQL aggregate on two columns with one conditionOracle SQL 在一个条件下聚合两列
【发布时间】:2018-11-30 09:11:23
【问题描述】:

有一个名为 ztp 的 Oracle 11g 表,数据如下:

TEAM       PERSON
---------- ----------
T1         P1
T1         P2
T1         P3
T1         P4
T2         P2
T2         P3
T2         P4
T3         P2
T3         P3
T3         P4
T3         P5
T4         P3
T4         P4
T5         P3
T5         P4
T5         P5

我想要一个 sql 查询来聚合具有以下条件的团队和人员:至少 3 个相同的人加入了至少 2 个相同的团队。

对于表 ztp 中的数据,我想要这样的 sql 查询输出:

TEAMS      PERSONS
---------- ----------
T1,T2,T3   P2,P3,P4
T3,T5      P3,P4,P5

我只知道这样的listagg:

SELECT TEAM, LISTAGG(PERSON,',') WITHIN GROUP (ORDER BY PERSON) AS PERSONS 
FROM ZTP 
GROUP BY TEAM

但结果只聚合了人,我不知道如何在一个条件下聚合两列。

【问题讨论】:

  • 欢迎来到 Stack Overflow!请将您的示例数据作为文本包含在您的问题中,并且不要发布它的屏幕截图。这使得其他人更容易复制数据并重现问题。此外,图片的链接可能会在一段时间后失效。
  • 如果您想获得快速帮助,最好将测试数据显示为文本。
  • 好的,我已将示例数据作为文本包含在内。

标签: sql oracle


【解决方案1】:

问题实际上并不在于两层聚合,如果那是您正在寻找三个或更多人的所有组合 - 所以listagg() 甚至对此也不起作用。

我怀疑有一种更简洁的方法,也许是使用示范条款;但是您可以使用a recursive CTE 创建每个团队至少三名成员的所有可能组合的列表:

with r (team, persons, members, last_person) as (
  select team, person, 1, person
  from ztp
  union all
  select r.team, r.persons ||','|| z.person, r.members + 1, z.person
  from r
  join ztp z on z.team = r.team and z.person > r.last_person
)
select * from r
where members >= 3;

anchor 子句只获取原始数据,加上一个members 列来计算有多少“聚合”值,以便我们以后可以使用它来过滤,以及最后看到的人,所以我们可以在 ' 之外使用它聚合'字符串。递归成员在同一团队中寻找更多人,将他们附加到列表中并增加成员计数,以便稍后再次过滤。

使用您的样本数据进行查询,确定五个团队中三个或更多人的 12 种不同组合:

TEAM PERSONS                           MEMBERS LAST_PERSON
---- ------------------------------ ---------- -----------
T1   P1,P3,P4                                3 P4         
T1   P1,P2,P4                                3 P4         
T1   P1,P2,P3                                3 P3         
T1   P2,P3,P4                                3 P4         
T2   P2,P3,P4                                3 P4         
T3   P2,P4,P5                                3 P5         
T3   P2,P3,P5                                3 P5         
T3   P2,P3,P4                                3 P4         
T3   P3,P4,P5                                3 P5         
T5   P3,P4,P5                                3 P5         
T1   P1,P2,P3,P4                             4 P4         
T3   P2,P3,P4,P5                             4 P5         

然后您可以使用listagg() 来对抗,使用having 子句过滤至少两个团队:

with r (team, persons, members, last_person) as (
  select team, person, 1, person
  from ztp
  union all
  select r.team, r.persons ||','|| z.person, r.members + 1, z.person
  from r
  join ztp z on z.team = r.team and z.person > r.last_person
)
select listagg(team, ',') within group (order by team) as teams, persons
from r
where members >= 3
group by persons
having count(persons) >= 2;

使用您的示例数据(此处作为另一个 CTE):

with ztp (team, person) as (
            select 'T1', 'P1' from dual
  union all select 'T1', 'P2' from dual
  union all select 'T1', 'P3' from dual
  union all select 'T1', 'P4' from dual
  union all select 'T2', 'P2' from dual
  union all select 'T2', 'P3' from dual
  union all select 'T2', 'P4' from dual
  union all select 'T3', 'P2' from dual
  union all select 'T3', 'P3' from dual
  union all select 'T3', 'P4' from dual
  union all select 'T3', 'P5' from dual
  union all select 'T4', 'P3' from dual
  union all select 'T4', 'P4' from dual
  union all select 'T5', 'P3' from dual
  union all select 'T5', 'P4' from dual
  union all select 'T5', 'P5' from dual
),
r (team, persons, members, last_person) as (
  select team, person, 1, person
  from ztp
  union all
  select r.team, r.persons ||','|| z.person, r.members + 1, z.person
  from r
  join ztp z on z.team = r.team and z.person > r.last_person
)
select listagg(team, ',') within group (order by team) as teams, persons
from r
where members >= 3
group by persons
having count(persons) >= 2;

TEAMS                          PERSONS                       
------------------------------ ------------------------------
T1,T2,T3                       P2,P3,P4                      
T3,T5                          P3,P4,P5                      

【讨论】:

  • 漂亮的答案,非常精确的逻辑。
  • @Alex Poole 非常感谢,确实如此。但是当 ZTP 记录 > 100k 时运行非常缓慢。那么sql能不能更有效呢?
  • 我想您可以预先过滤至少有三个人和至少两个团队中的人的团队,但是如果停止主查询使用的索引,这可能只会让事情变得更糟。
【解决方案2】:

这个解决方案有点简单,可能会导致大量数据出现一些性能问题,但它仍然可以帮助您创建更精确的解决方案。 我们的想法是创建成员数量超过 3 人的小组,并计算这些小组参加的团队数量。

假设:团队或人名中没有逗号。

with src as (
select 'T1' as team, 'P1' as person from dual union all
select 'T1','P2' from dual union all
select 'T1','P3' from dual union all
select 'T1','P4' from dual union all
select 'T2','P2' from dual union all
select 'T2','P3' from dual union all
select 'T2','P4' from dual union all
select 'T3','P2' from dual union all
select 'T3','P3' from dual union all
select 'T3','P4' from dual union all
select 'T3','P5' from dual union all
select 'T4','P3' from dual union all
select 'T4','P4' from dual union all
select 'T5','P3' from dual union all
select 'T5','P4' from dual union all
select 'T5','P5' from dual
),
cnct as (
select level lv, team, lpad(' ',level,'  ')||person, sys_connect_by_path(person,',') pth
  from src s1
 connect by person > prior person and team=prior team
)
select tm, ltrim(pth,',')
  from (
    select pth, LISTAGG(team,',') within group ( order by team ) tm
      from cnct  
     where lv >= 3                               
    group by pth
)
where instr(tm,',') > 0

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-10
    • 2017-08-24
    • 2021-07-05
    • 1970-01-01
    • 2020-07-09
    • 2021-11-01
    相关资源
    最近更新 更多