【问题标题】:Oracle ListaGG, Top 3 most frequent values, given in one column, grouped by IDOracle ListaGG,前 3 个最常见的值,在一列中给出,按 ID 分组
【发布时间】:2016-06-30 15:48:04
【问题描述】:

我有一个关于 SQL 查询的问题,它可以在“普通”SQL 中完成,但我确信我需要使用一些组连接(不能使用 MySQL)所以第二个选项是 ORACLE 方言,因为会有是Oracle数据库。假设我们有以下实体:

表格:兽医访问​​

Visit_Id, 
Animal_id, 
Veterinarian_id, 
Sickness_code

假设有 100 次访问 (100 visit_id),每个 animal_id 访问大约 20 次。

我需要创建一个 SELECT ,按 Animal_id 分组,包含 3 列

  • animal_id
  • 第二个显示该特定动物的流感就诊总数(假设是流感,sickness_code = 5)
  • 第 3 列显示每种动物的前三个疾病代码(该特定动物 ID 的前 3 个最常见的代码)

怎么做?第一列和第二列很容易,但第三列呢?我知道我需要使用 Oracle 的 LISTAGG、OVER PARTITION BY、COUNT 和 RANK,我试图将它结合在一起,但没有达到我的预期:(这个查询应该是什么样子?

【问题讨论】:

  • 您好!欢迎来到 StackOverflow!您能否发布一小部分示例数据以及您希望输出的示例?
  • 请阅读How-to-Ask 这里是START 了解如何提高问题质量并获得更好答案的好地方。
  • How to create a Minimal, Complete, and Verifiable example 例如,而不是 100 次访问使用 10。两只动物,每只动物获得 5 次访问。并以 TOP 2 结果显示结果。
  • 在平局的情况下,期望的输出是什么?例如,如果第三和第四最常见的代码都出现 11 次怎么办?应该选择哪个代码?另外,如果两个代码并列第一,应该先显示哪个?

标签: sql oracle rank listagg


【解决方案1】:

这里是示例数据

create table VET as
select 
rownum+1 Visit_Id, 
mod(rownum+1,5) Animal_id, 
cast(NULL as number)  Veterinarian_id, 
trunc(10*dbms_random.value)+1 Sickness_code
from dual
connect by level <=100;

查询

基本上子查询执行以下操作:

汇总计数并计算流感计数(在动物的所有记录中)

计算排名(如果您只需要 3 条记录,请使用 ROW_NUMBER - 请参阅下面的讨论)

过滤前 3 个排名

LISTAG汇总结果

with agg as (
select Animal_id, Sickness_code, count(*) cnt,
sum(case when SICKNESS_CODE = 5 then 1 else 0 end) over (partition by animal_id) as cnt_flu
from vet
group by Animal_id, Sickness_code
), agg2 as (
select ANIMAL_ID, SICKNESS_CODE, CNT, cnt_flu,
rank() OVER (PARTITION BY ANIMAL_ID ORDER BY cnt DESC) rnk
from agg
), agg3 as (
select ANIMAL_ID, SICKNESS_CODE, CNT, CNT_FLU, RNK
from agg2
where rnk <= 3
)
select 
ANIMAL_ID, max(CNT_FLU) CNT_FLU,
LISTAGG(SICKNESS_CODE||'('||CNT||')', ', ') WITHIN GROUP (ORDER BY rnk)  as   cnt_lts
from agg3
group by ANIMAL_ID 
order by 1;

给予

 ANIMAL_ID    CNT_FLU CNT_LTS                                     
---------- ---------- ---------------------------------------------
         0          1 6(5), 1(4), 9(3)                              
         1          1 1(5), 3(4), 2(3), 8(3)                        
         2          0 1(5), 10(3), 4(3), 6(3), 7(3)                 
         3          1 5(4), 2(3), 4(3), 7(3)                        
         4          1 2(5), 10(4), 1(2), 3(2), 5(2), 7(2), 8(2) 

我故意显示 Sickness_code(计数访问)来证明前 3 名可能有您应该处理的关系。 检查 RANK 功能。在这种情况下,使用 ROW_NUMBER 不是确定性的。

【讨论】:

    【解决方案2】:

    我认为最自然的方法是使用两个级别的聚合,以及一些窗口函数:

    select vas.animal,
           sum(case when sickness_code = 5 then cnt else 0 end) as numflu,
           listagg(case when seqnum <= 3 then sickness_code end, ',') within group (order by seqnum) as top3sicknesses
    from (select animal, sickness_code, count(*) as cnt,
                 row_number() over (partition by animal order by count(*) desc) as seqnum
          from visits
          group by animal, sickness_code
         ) vas
    group by vas.animal;
    

    这利用了listagg() 忽略NULL 值的事实。

    【讨论】:

    • 优雅的解决方案,忽略 NULL 并保存子查询 (+1)。请在row_number 中修复order by count(*)。 (cnt 未定义)
    • @MarmiteBomber 。 . .谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-29
    • 1970-01-01
    • 2021-08-30
    相关资源
    最近更新 更多