如何在 Oracle 中进行分组

Posted

技术标签:

【中文标题】如何在 Oracle 中进行分组【英文标题】:How to group-by in Oracle 【发布时间】:2021-03-10 00:22:27 【问题描述】:

我在下面有一个类似 [Original] 的表格。 我想按 [结果] 之类的分组字段求和。 有没有人有想法进行此查询? 提前感谢您的帮助。

WITH t1 as (
      SELECT 1 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 2 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 3 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 4 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 5 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 6 AS ID, 'B' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 7 AS ID, 'A' AS FIELD, 3 AS VAL FROM dual
UNION SELECT 8 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 9 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
)
SELECT *
FROM t1




[Original Data]
ID  FIELD   VAL
1   A   1
2   A   2
3   A   1
4   B   2
5   B   2
6   B   1
7   A   3
8   A   2
9   A   1


[Result]
ID  FIELD   VAL
1   A   4
4   B   5
7   A   6

【问题讨论】:

您已经尝试过哪个查询,有什么问题? 当然,Oracle 确实有一个想法、一个描述,而且,some examples 就在GROUP BY clause 的文档部分。请描述您的问题。 通过使用普通的group by,它只返回A,B的两条记录。在这个问题中,我想处理'A'的两组。感谢 Tejash、MT0、GMB,现在我知道这被称为间隙和孤岛问题。感谢您的加入。 【参考方案1】:

这是孤岛和差距问题,您可以使用如下分析函数:

SQL> WITH t1 as (
  2        SELECT 1 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
  3  UNION SELECT 2 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
  4  UNION SELECT 3 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
  5  UNION SELECT 4 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
  6  UNION SELECT 5 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
  7  UNION SELECT 6 AS ID, 'B' AS FIELD, 1 AS VAL FROM dual
  8  UNION SELECT 7 AS ID, 'A' AS FIELD, 3 AS VAL FROM dual
  9  UNION SELECT 8 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
 10  UNION SELECT 9 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
 11  )
 12  SELECT MIN(ID) AS ID, FIELD, SUM(VAL)
 13    FROM (SELECT T1.*,
 14     SUM(CASE WHEN LAG_FIELD = FIELD THEN 0 ELSE 1 END)
 15  OVER (ORDER BY ID) AS SM
 16    FROM (SELECT T1.*,
 17     LAG(FIELD) OVER (ORDER BY ID) AS LAG_FIELD
 18    FROM t1
 19  ) T1
 20  )
 21  GROUP BY FIELD, SM
 22  ORDER BY 1;

        ID F   SUM(VAL)
---------- - ----------
         1 A          4
         4 B          5
         7 A          6

SQL>

【讨论】:

通过在没有任何描述的情况下获取结果数据和输入数据来对问题进行逆向工程是非常棒的。我真的很佩服这个解决方案和你的技能,太棒了! 这是非常好的方法。我喜欢你做的方式。这是一个没有任何新功能的绝妙解决方案。【参考方案2】:

这确实是一个孤岛问题。我认为这里最简单的方法是使用行号之间的差异来识别相邻行的组:

select min(id) as id, field, sum(val) as val
from (
    select t1.*,
        row_number() over(order by id) rn1,
        row_number() over(partition by field order by id) rn2
    from t1
) t
group by field, rn1 - rn2
order by min(id)

如果 id 总是无间隙地递增,这就更简单了:

select min(id) as id, field, sum(val) as val
from (
    select t1.*,
        row_number() over(partition by field order by id) rn
    from t1
) t
group by field, id - rn
order by min(id)

【讨论】:

感谢您,我发现这是缝隙和孤岛问题。能接触到很多人的担忧,我真的很高兴。您的解决方案和其他人一样非常好。您的解决方案给我留下了深刻的印象。【参考方案3】:

从 Oracle 12 开始,您可以非常简单地使用 MATCH_RECOGNIZE

WITH t1 as (
      SELECT 1 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 2 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 3 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 4 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 5 AS ID, 'B' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 6 AS ID, 'B' AS FIELD, 1 AS VAL FROM dual
UNION SELECT 7 AS ID, 'A' AS FIELD, 3 AS VAL FROM dual
UNION SELECT 8 AS ID, 'A' AS FIELD, 2 AS VAL FROM dual
UNION SELECT 9 AS ID, 'A' AS FIELD, 1 AS VAL FROM dual
)
SELECT *
FROM   t1
MATCH_RECOGNIZE (
  ORDER BY id
  MEASURES
    FIRST( id ) AS id,
    FIRST( field ) AS field,
    SUM( val ) AS total
  ONE ROW PER MATCH
  PATTERN( same_field+ )
  DEFINE same_field AS FIRST(field) = field 
)

哪些输出:

身份证 |领域 |全部的 -: | :---- | ----: 1 |一个 | 4 4 |乙| 5 7 |一个 | 6

db小提琴here

【讨论】:

感谢您的解决方案。即使我的神谕是 11,小提琴表明你是对的。感谢您的出色回答。

以上是关于如何在 Oracle 中进行分组的主要内容,如果未能解决你的问题,请参考以下文章

如何在不进行分组的情况下在Oracle中向SQL结果添加虚拟行

如何在不使用 GROUP BY 或 PARTITION BY 的情况下对 Oracle SQL 中的数据进行分组

如何对具有聚合函数的 Oracle 查询进行分组

如何在 Oracle SQL 上进行查询以获取时间间隔,按特定字段分组

Oracle中一个表数据大概有一亿条,现在需要进行数据按照三个字段进行分组查询数据统计,如何查询较快

SQL (Oracle):按特定列分组