oracle group by 当值为零时,聚合 = 0

Posted

技术标签:

【中文标题】oracle group by 当值为零时,聚合 = 0【英文标题】:oracle group by when value is zero, aggregate = 0 【发布时间】:2021-09-21 17:26:35 【问题描述】:

我正在尝试构建一个查询,但它花费了我太多时间来解决它。

Oracle 数据库 v18

这是我的桌子1

Date1 tagname Value
01/01/2021 0:01 a 2
01/01/2021 0:02 a 4
01/01/2021 0:01 b 2
01/01/2021 0:02 b 4
01/01/2021 0:01 c 2
01/01/2021 0:02 c 4
02/01/2021 0:01 a 0
02/01/2021 0:02 a 0
02/01/2021 0:01 b 2
02/01/2021 0:02 b 4
02/01/2021 0:01 c 2
02/01/2021 0:02 c 4

我每天做的平均数

select avg(value) value, tagname, to_date(date1,'dd/MM/yyyy') 
from table1 
group by date1, tagname

结果:

Date1 tagname Value
01/01/2021 a 3
01/01/2021 b 3
01/01/2021 c 3
02/01/2021 a 0
02/01/2021 b 3
02/01/2021 c 3

现在我需要添加一个新的标记名

select sum(value), 'newtag' tagname 
from result
where tagname= 'a' or tagname = 'b' or tagname= 'c'
group by date1

但是当 a=0 新标签值 = 0

我怎样才能归档这个?

例子

Date1 tagname Value
01/01/2021 a 3
01/01/2021 b 3
01/01/2021 c 3
01/01/2021 newtag 9
02/01/2021 a 0
02/01/2021 b 3
02/01/2021 c 3
02/01/2021 newtag 0

我可以在这个查询中使用 case 吗?

提前致谢

编辑:table1有更多的tagname,但只需要sum(a+b+c)

【问题讨论】:

【参考方案1】:

所以,当然,UNION ALL 很容易做到这一点。我猜您担心的是您不想通读您的表格两次(一次用于计算日期/标签聚合,另一次用于计算日期聚合)。

任何时候您想在多个级别聚合查询结果,您至少应该考虑GROUPING SETS 功能。

在您的情况下,诀窍不是多级聚合。相反,您希望第二级聚合(按日期)是在第一级(按日期/标签)计算的聚合的SUM()

为此,您可以在完成任何聚合之前使用窗口函数按日期/标签计算AVG()。这使得以后可以SUM()他们。这是一个工作示例(Oracle 12.1):

-- Create table with test data
create table my_table1 (Date1,  tagname,    Value) AS (
SELECT TO_DATE('01/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'a',    2 FROM DUAL UNION ALL
SELECT TO_DATE('01/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'a',    4 FROM DUAL UNION ALL
SELECT TO_DATE('01/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'b',    2 FROM DUAL UNION ALL
SELECT TO_DATE('01/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'b',    4 FROM DUAL UNION ALL
SELECT TO_DATE('01/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'c',    2 FROM DUAL UNION ALL
SELECT TO_DATE('01/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'c',    4 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'a',    0 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'a',    0 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'b',    2 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'b',    4 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'c',    2 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'c',    4 FROM DUAL
)
;

-- Compute the averages and the use GROUPING SETS to use those those
-- averages conditionally at multiple levels of aggregation
with date_tag_summary as (
select trunc(date1) date1, tagname, avg(value) avg_value
from my_table1
group by trunc(date1), tagname )
select date1, 
       case when grouping(tagname)=1 then 'newtag' ELSE tagname END tagname, 
       case when grouping(tagname)=1 AND COUNT(DECODE(avg_value,0,1,NULL)) > 0 THEN 0
            when grouping(tagname)=1 THEN sum(avg_value)
            ELSE min(avg_value) END value
from date_tag_summary
group by grouping sets ( (date1, tagname), (date1) )
order by 1,2;
+-----------+---------+-------+
|   DATE1   | TAGNAME | VALUE |
+-----------+---------+-------+
| 01-JAN-21 | a       |     3 |
| 01-JAN-21 | b       |     3 |
| 01-JAN-21 | c       |     3 |
| 01-JAN-21 | newtag  |     9 |
| 02-JAN-21 | a       |     0 |
| 02-JAN-21 | b       |     3 |
| 02-JAN-21 | c       |     3 |
| 02-JAN-21 | newtag  |     0 |
+-----------+---------+-------+

并且,为了说明数据没有被读取两次,以下是该查询的执行计划:

-----------------------------------------------------------------------------------
| Id  | Operation             | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |           |       |       |     6 (100)|          |
|   1 |  SORT ORDER BY        |           |     3 |    63 |     6  (50)| 00:00:01 |
|   2 |   SORT GROUP BY ROLLUP|           |     3 |    63 |     6  (50)| 00:00:01 |
|   3 |    VIEW               |           |     9 |   189 |     4  (25)| 00:00:01 |
|   4 |     HASH GROUP BY     |           |     9 |   117 |     4  (25)| 00:00:01 |
|   5 |      TABLE ACCESS FULL| MY_TABLE1 |    12 |   156 |     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------------

【讨论】:

谢谢,我在原帖中没有这么说,但是我有更多的标签,但只需要求和 (a+b+c) 有没有办法只使用指定的标签进行操作?,如果我想用'b'过滤,我怎么知道我用'a'过滤?【参考方案2】:

一种方法使用cross join 生成行,然后引入现有结果:

select d.date1, t.tagname, avg(value) value
from (select distinct to_date(date1, 'dd/MM/yyyy') as date1 from table1
     ) d cross join
     (select 'a' as tagname from dual union all
      select 'b' as tagname from dual union all
      select 'c' as tagname from dual union all
      select 'd' as tagname from dual
     ) t
     table1 t1
     on to_date(t1.date1, 'dd/MM/yyyy') = d.date1 and
        t1.tagname = t.tagname
group by date1, tagname

【讨论】:

谢谢,但我看不到 d 标签名中的总和在哪里,反正我不想重复所有标签,因为这是一个很长的列表。【参考方案3】:

您可以使用grouping sets,然后用分析函数计算的平均值总和替换组的平均总计。

select /*+ gather_plan_statistics */
  trunc(date1) as dt
  , case grouping_id(tagname)
      when 0
      then tagname
      else 'newtag'
    end as tagname
  , case grouping_id(tagname)
      when 0
      then avg(value)
      else (
        /*Total sum except total avg*/
        sum(avg(value)) over(
          partition by trunc(date1)
        ) - avg(value))
          * decode(min(avg(value)) over(partition by trunc(date1)), 0, 0, 1)
    end as val
      
from a
group by grouping sets( (trunc(date1), tagname), trunc(date1))
DT |标签名 |价值 :-------- | :-------- | --: 21 年 1 月 1 日 |一个 | 3 21 年 1 月 1 日 |乙 | 3 21 年 1 月 1 日 | c | 3 21 年 1 月 1 日 |新标签 | 9 21 年 1 月 2 日 |一个 | 0 21 年 1 月 2 日 |乙 | 3 21 年 1 月 2 日 | c | 3 21 年 1 月 2 日 |新标签 | 0

db小提琴here

【讨论】:

@MatthewMcPeak 没有魔法:它只是总结了所有的平均值,总行减去该行的平均值(从结果中排除它)。 7 而不是 4 怎么样:我无法在附加的小提琴中重现它。但是,是的,它在单个查询中或多或少是相同的,而不是按相同的列分组两次(不是更好/更差)。 谢谢,我刚刚编辑了我的原始帖子,我有更多标签,只需要求和(a+b+c)【参考方案4】:

您可以使用以下查询。当然是在SQL中设置的

;WITH cte AS
  (SELECT convert(date,date1) as date1,tagname,avg(value) value
   FROM table1
   GROUP BY convert(date,date1),tagname)

select date1,tagname,
   case when tagname = 'newtag' 
   then 
       case (select cte.value from cte where cte.date1 = result.date1 and cte.tagname = 'a') 
       when 0 then 0 
       else (select top 1 sum(c.value) from cte c where convert(date,c.date1,103) = result.date1)
       end
   else value end

from
(select date1,tagname,value ,ROW_NUMBER() over(partition by date1,tagname order by date1) as seq
from
  (
    select convert(date,date1) as date1,tagname,avg(value) as value
    from table1
    group by convert(date,date1),tagname


    union all


    select convert(date,date1),'newtag', 0
    from table1
    group by convert(date,date1),tagname
  ) T
) result
where result.seq = 1
order by convert(date,date1)

【讨论】:

【参考方案5】:

每天的第一个平均值

with avgday as (select avg(value) value, tagname, to_date(date1,'dd/MM/yyyy') 
from table1 group by date1, tagname)

将行转化为列,做一个case来过滤和操作。

with query1 as (SELECT *  FROM avgday  PIVOT (  MAX(value) FOR tagname IN ('a','b','c')))
select  date1, case 
when   query1.a=0 
 then 0 
else a + b + c end value, 
'newtag' tagname
 from query1 

我终于想出了一个解决方案,当然这不是最好的答案,但它解决了我的问题

【讨论】:

以上是关于oracle group by 当值为零时,聚合 = 0的主要内容,如果未能解决你的问题,请参考以下文章

聚合函数 和 group by

oracle语句中的聚合函数以及分组group by的使用实例

Oracle_group by分组查询_深入

mysql 可以group by 两个字段吗

group by 后面可以带两个字段吗

Oracle 中关于 group by 的那些坑