GROUP BY TRUNC 的链(日期)
Posted
技术标签:
【中文标题】GROUP BY TRUNC 的链(日期)【英文标题】:Chain of GROUP BY TRUNC(date) 【发布时间】:2018-01-23 15:26:47 【问题描述】:我有一个包含每小时数据和价值的简单表格。我想计算每个月每日最大值的平均值。 这个查询起初看起来很简单:
WITH daily_max AS
(
SELECT TRUNC(the_date, 'DD') as my_day, MAX(value) AS value
FROM my_data
GROUP by TRUNC(the_date, 'DD')
)
SELECT trunc(my_day, 'MM'), AVG(value)
FROM daily_max
GROUP BY trunc(my_day, 'MM')
order by 1
;
但是,我在第一列中有很多“重复”(每天一个):
01/01/2017 00:00:00 95
01/01/2017 00:00:00 90
01/01/2017 00:00:00 99
01/01/2017 00:00:00 96
01/01/2017 00:00:00 94
01/01/2017 00:00:00 97
01/01/2017 00:00:00 96
01/01/2017 00:00:00 86
01/01/2017 00:00:00 98
01/01/2017 00:00:00 98
01/02/2017 00:00:00 97
01/02/2017 00:00:00 93
01/02/2017 00:00:00 100
01/02/2017 00:00:00 98
01/02/2017 00:00:00 94
01/02/2017 00:00:00 99
01/02/2017 00:00:00 94
01/02/2017 00:00:00 95
01/02/2017 00:00:00 99
第一个子查询按预期返回每日最大值。
我怀疑 DATE 数据类型存在奇怪的行为,但即使我在日期上使用 TO_CHAR 函数,我也有相同的行为。 GROUP BY 语句中的表达式如何导致具有相同值的多行?
with daily_max AS
(
SELECT TRUNC(the_date, 'DD') as my_day, MAX(value) AS value
FROM my_data
GROUP by TRUNC(the_date, 'DD')
)
SELECT TO_CHAR(trunc(my_day, 'MM')), AVG(value)
FROM daily_max
GROUP BY TO_CHAR(trunc(my_day, 'MM'))
order by 1
;
为了增加我的困惑,当我在第一个子查询中将日期转换为时间戳时,结果是我所期望的:
with daily_max AS
(
SELECT CAST(TRUNC(the_date , 'DD') AS timestamp) as my_day, MAX(value) AS value
FROM my_data
GROUP by TRUNC(the_date , 'DD')
)
SELECT trunc(my_day, 'MM') AS the_month, AVG(value)
FROM daily_max
GROUP BY trunc(my_day, 'MM')
order by 1
;
01/01/2017 00:00:00 94.9
01/02/2017 00:00:00 95.78571428571428571428571428571428571429
01/03/2017 00:00:00 95.38709677419354838709677419354838709677
01/04/2017 00:00:00 94.9
01/05/2017 00:00:00 95.32258064516129032258064516129032258065
01/06/2017 00:00:00 96.46666666666666666666666666666666666667
01/07/2017 00:00:00 96.16129032258064516129032258064516129032
01/08/2017 00:00:00 96.16129032258064516129032258064516129032
01/09/2017 00:00:00 96.13333333333333333333333333333333333333
01/10/2017 00:00:00 95.87096774193548387096774193548387096774
01/11/2017 00:00:00 97.3
01/12/2017 00:00:00 96.90322580645161290322580645161290322581
01/01/2018 00:00:00 96.43478260869565217391304347826086956522
我可能错过了一些愚蠢的事情,但谁能向我解释这些行为?
查询生成测试表:
CREATE TABLE my_data
AS
SELECT TRUNC (SYSDATE - ROWNUM/24, 'HH') as the_date, ROUND(DBMS_RANDOM.value(0,100),0) AS value
FROM DUAL
CONNECT BY ROWNUM < 366*24
;
【问题讨论】:
您的第一组重复项不是每天一个。 按照您的建议创建 my_data 并尝试您的第一个 sql 后,无法遇到任何问题。 无法复制 - Oracle LiveSQL 我可以在 12.1 中重现,但不能在 11.2 或 12.2 中重现,所以似乎是一个错误。您使用的是哪个版本和补丁级别? 似乎与内联视图以及 CTE 一起发生,Posisbly 错误 20537092;解决方法似乎可以避免这种情况? 【参考方案1】:这似乎是bug 20537092;它在 12.1.0.2(使用 CTE 或内联视图)中可重现,但在 11.2.0.4 或 12.2.0.1 中可重现。
该文档中的解决方法似乎可以解决此问题;设置后运行您的示例
alter session set "_optimizer_aggr_groupby_elim"=false;
在以前没有的 12.1 会话中给出合理的结果:
TRUNC(MY_DAY,'MM') AVG(VALUE)
------------------- ----------
2017-01-01 00:00:00 95.5
2017-02-01 00:00:00 95.6428571
2017-03-01 00:00:00 95.3225806
2017-04-01 00:00:00 95.6666667
2017-05-01 00:00:00 97.0322581
2017-06-01 00:00:00 95.7
2017-07-01 00:00:00 95.0967742
2017-08-01 00:00:00 96.1935484
2017-09-01 00:00:00 94.9333333
2017-10-01 00:00:00 96
2017-11-01 00:00:00 96.9333333
2017-12-01 00:00:00 95.3870968
2018-01-01 00:00:00 95.0434783
重写查询以避免嵌套 group-by 可能更实用 - 当然,这取决于您真正的复杂程度,以及您是否可以修改相关会话或数据库初始化设置,或修补它。
对于您的(可能是简化的)示例,在没有应用解决方法的新会话中,用不同的和分析版本替换内部聚合/分组似乎有效;这有点难看,可能不适合你的实际情况:
WITH daily_max AS
(
SELECT DISTINCT TRUNC(the_date, 'DD') as my_day,
MAX(value) OVER (PARTITION BY TRUNC(the_date, 'DD')) AS value
FROM my_data
)
SELECT trunc(my_day, 'MM'), AVG(value)
FROM daily_max
GROUP BY trunc(my_day, 'MM')
order by 1
;
和往常一样,仅仅因为它看起来像这个错误并不意味着它一定是;您可能需要提出服务请求以获得确认,尤其是在修补之前。
【讨论】:
【参考方案2】:我无法解释您看到的行为。您可以尝试在没有 CTE 的情况下以不同的方式编写逻辑:
SELECT TRUNC(my_day, 'MM'),
SUM(value) / COUNT(DISTINCT TRUNC(the_date, 'DD'))
FROM my_data
GROUP BY TRUNC(my_day, 'MM')
ORDER BY 1;
【讨论】:
【参考方案3】:也许 trunc() 不返回日期...
WITH daily_max AS
(
SELECT to_date(TRUNC(the_date, 'DD')) as my_day, MAX(value) AS value
FROM jfl_test
group by TRUNC(the_date, 'DD')
)
SELECT trunc(my_day, 'MM'), AVG(value)
FROM daily_max
GROUP BY trunc(my_day, 'MM')
order by 1
;
【讨论】:
trunc()
does return a date。在日期上调用to_date()
只是进行两次基于 NLS 的转换,将日期隐式转换为字符串,然后显式返回日期。以上是关于GROUP BY TRUNC 的链(日期)的主要内容,如果未能解决你的问题,请参考以下文章