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 的链(日期)的主要内容,如果未能解决你的问题,请参考以下文章

R - 基于日期列使用 group_by 的平均计算?

GROUP BY 有 MAX 日期

SQL Group By 每个日期的总和并使用最大日期

SQL 到 GROUP BY 计算日期

SqlAlchemy group_by 并返回最大日期

Django GROUP BY strftime 日期格式