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
,然后用分析函数计算的平均值总和替换组的平均总计。
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 日 |新标签 | 0select /*+ 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))
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的主要内容,如果未能解决你的问题,请参考以下文章