【问题标题】:Oracle: Need to calculate rolling average for past 3 months where we have more than one submission per monthOracle:需要计算过去 3 个月的滚动平均值,其中我们每月有多个提交
【发布时间】:2018-05-03 06:11:24
【问题描述】:

我在 oracle 中看到了很多滚动平均值的示例,但都做了我想要的。

这是我的原始数据

DATE            SCORE   AREA
----------------------------
01-JUL-14       60      A
01-AUG-14       45      A
01-SEP-14       45      A
02-SEP-14       50      A
01-OCT-14       30      A
02-OCT-14       45      A
03-OCT-14       50      A
01-JUL-14       60      B
01-AUG-14       45      B
01-SEP-14       45      B
02-SEP-14       50      B
01-OCT-14       30      B
02-OCT-14       45      B
03-OCT-14       50      B

这是我的滚动平均值的预期结果

MMYY        AVG     AREA
-------------------------
JUL-14      60      A
AUG-14      52.5    A
SEP-14      50      A
OCT-14      44      A
JUL-14      60      B
AUG-14      52.5    B
SEP-14      50      B
OCT-14      44      B

我需要它的工作方式是,对于每个 MMYY,我需要回顾 3 个月,并平均每个部门的分数。例如,

对于 OCT 的 A 区,从 10 月开始的最近 3 个月内,有 6 项研究,(45+45+50+30+45+50)/6 = 44.1

通常我会这样写查询

SELECT
  AREA, 
  TO_CHAR(T.DT,'MMYY') MMYY,
  ROUND(AVG(SCORE)
    OVER (PARTITION BY AREA ORDER BY TO_CHAR(T.DT,'MMYY') ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),1)
    AS AVG 
    FROM T

这将查看过去 3 个条目,而不是过去 3 个月

【问题讨论】:

  • Average = total sum / total count,不是吗?查看答案。

标签: sql oracle


【解决方案1】:

一种方法是将聚合函数与分析函数混合使用。平均值的关键思想是避免使用avg(),而是使用sum() 除以count(*)

  SELECT AREA, TO_CHAR(T.DT, 'MMYY') AS MMYY,
         SUM(SCORE) / COUNT(*) as AvgScore,
         SUM(SUM(SCORE)) OVER (PARTITION BY AREA ORDER BY MAX(T.DT) ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) / SUM(COUNT(*)) OVER (PARTITION BY AREA ORDER BY MAX(T.DT) ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
  FROM t
  GROUP BY AREA, TO_CHAR(T.DT, 'MMYY') ;

注意order by 子句。如果您的数据跨越数年,那么使用 MMYY 格式会带来问题。月份最好使用 YYYY-MM 等格式,因为字母顺序与自然顺序相同。

【讨论】:

  • 这正是我对 avg() 的想法,最好使用 sum 并除以 count。
  • 谢谢,但 AvgScore 是多少?
  • @totalitarian AvgScore 是列别名,是在结果集中命名列的一种方式。
  • @GordonLinoff 是的,我只是想知道该列的计算方式,因为它不需要
  • @totalitarian 。 . .考虑到您想要三个月的平均值,这对我来说似乎很有趣。
【解决方案2】:

您还可以指定范围,而不仅仅是行。

SELECT
  AREA, 
  TO_CHAR(T.DT,'MMYY') MMYY,
  ROUND(AVG(SCORE)
    OVER (PARTITION BY AREA 
      ORDER BY DT RANGE BETWEEN INTERVAL '3' MONTH PRECEDING AND CURRENT ROW))
    AS AVG 
    FROM T

由于CURRENT ROW 是默认设置,因此ORDER BY DT RANGE INTERVAL '3' MONTH PRECEDING 也应该可以正常工作。也许你需要做一些微调,我没有测试每月 28/29/30/31 天问题的行为。

查看 Oracle Windowing Clause 了解更多详情。

【讨论】:

  • ORA-00907:缺少右括号 00907. 00000 - “缺少右括号” *原因:*操作:第 16 行错误,第 22 列
  • 什么是 DATE 我没有名为 DATE 的列
  • 避免一个月中的天数问题的诀窍是使用ORDER BY TRUNC(<datecolumn>,'MM') - 这样RANGE 只对所有都是当月第一天的值进行操作。
  • @totalitarian,您的样本数据显示DATE;SCORE;AREA。在这种情况下是DT
【解决方案3】:
SQL> WITH DATA AS(
  2  SELECT to_date('01-JUL-14','DD-MON-RR')  dt,     60   score,    'A' area FROM dual UNION ALL
  3  SELECT to_date('01-AUG-14','DD-MON-RR')  dt,       45      score,    'A' area FROM dual UNION ALL
  4  SELECT to_date('01-SEP-14','DD-MON-RR')  dt,       45      score,    'A' area FROM dual UNION ALL
  5  SELECT to_date('02-SEP-14','DD-MON-RR')  dt,       50      score,    'A' area FROM dual UNION ALL
  6  SELECT to_date('01-OCT-14','DD-MON-RR')  dt,       30      score,    'A' area FROM dual UNION ALL
  7  SELECT to_date('02-OCT-14','DD-MON-RR')  dt,       45      score,    'A' area FROM dual UNION ALL
  8  SELECT to_date('03-OCT-14','DD-MON-RR')  dt,      50      score,    'A' area FROM dual UNION ALL
  9  SELECT to_date('01-JUL-14','DD-MON-RR')  dt,       60      score,    'B' area FROM dual UNION ALL
 10  SELECT to_date('01-AUG-14','DD-MON-RR')  dt,       45      score,    'B' area FROM dual UNION ALL
 11  SELECT to_date('01-SEP-14','DD-MON-RR')  dt,       45      score,    'B' area FROM dual UNION ALL
 12  SELECT to_date('02-SEP-14','DD-MON-RR')  dt,       50      score,    'B' area FROM dual UNION ALL
 13  SELECT to_date('01-OCT-14','DD-MON-RR')  dt,       30      score,    'B' area FROM dual UNION ALL
 14  SELECT to_date('02-OCT-14','DD-MON-RR')  dt,       45      score,    'B' area FROM dual UNION ALL
 15  SELECT to_date('03-OCT-14','DD-MON-RR')  dt,       50      score,    'B' area FROM dual)
 16  SELECT   TO_CHAR(T.DT, 'MON-RR') AS MMYY,
 17           round(
 18           SUM(SUM(SCORE)) OVER (PARTITION BY AREA ORDER BY MAX(T.DT) ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)/
 19           SUM(COUNT(*)) OVER (PARTITION BY AREA ORDER BY MAX(T.DT) ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),1)
 20           AS avg_score,
 21           AREA
 22    FROM data t
 23    GROUP BY AREA, TO_CHAR(T.DT, 'MON-RR')
 24  /

MMYY    AVG_SCORE A
------ ---------- -
JUL-14         60 A
AUG-14       52.5 A
SEP-14         50 A
OCT-14       44.2 A
JUL-14         60 B
AUG-14       52.5 B
SEP-14         50 B
OCT-14       44.2 B

8 rows selected.

SQL>

从下一次开始,我希望您提供createinsert 语句,这样我们就不必花时间准备test case

还有,为什么是YY 格式?你没有看到Y2K 错误吗?请使用YYYY 格式。

【讨论】:

  • 也许他只是用他的默认日期格式复制/粘贴。将示例表粘贴到SQLFiddle 只需几秒钟即可生成数据。
  • 谢谢。我不知道 sql fiddle 技术。
  • 需要注意的一点是,ROWS BETWEEN 将是前 2 个月有数据到当前月份的平均值。这意味着,例如,如果区域 A 在 2014 年 8 月没有数据,则 10 月的滚动平均值将为 7 月、9 月和 10 月。如果这不是可取的,请考虑使用类似ORDER BY TRUNC(MIN(T.DT),'MM') RANGE BETWEEN INTERVAL '2' MONTH PRECEDING AND CURRENT ROW
猜你喜欢
  • 2019-03-03
  • 1970-01-01
  • 1970-01-01
  • 2019-12-16
  • 2012-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多