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

Posted

技术标签:

【中文标题】SQL Group By 每个日期的总和并使用最大日期【英文标题】:SQL Group By with Sum for each date and using max date 【发布时间】:2016-05-09 11:08:03 【问题描述】:

我见过很多类似的问题,但没有什么能完全解决我的具体问题。

我有一个表格,为每个帐户存储多个头寸。更改存储为增量。因此,以第 1 天为例...

AC_ID | POS_ID | ASAT       | VAL
    1 |      1 | 2016-01-01 | 100
    1 |      2 | 2016-01-01 | 200

2016 年 1 月 1 日AC_ID 1 的总值为 300。第二天可能会更新为...

AC_ID | POS_ID | ASAT       | VAL
    1 |      1 | 2016-01-01 | 100
    1 |      2 | 2016-01-01 | 200
    1 |      2 | 2016-01-02 | 250

现在AC_ID 1 的总值是350。这是因为POS_ID 2 的新记录覆盖了之前的记录,但POS_ID 1 的值没有改变。为了删除POS_ID 1,该表将更改为...

AC_ID | POS_ID | ASAT       | VAL
    1 |      1 | 2016-01-01 | 100
    1 |      2 | 2016-01-01 | 200
    1 |      2 | 2016-01-02 | 250
    1 |      1 | 2016-01-03 | 0

现在该值在第 3 天变为 250。

我可以使用这样的子查询计算任何给定日期的值

SELECT SUM(VAL) FROM POSITION P1
WHERE P1.ASAT = 
  (SELECT MAX(P2.ASAT) FROM POSITION P2
   WHERE P1.AC_ID  = P2.AC_ID
   AND   P1.POS_ID = P2.POS_ID
   AND   P2.DATE <= [CHOSEN DATE])

我现在想做的是编写一个查询,它将为每个ASAT 提供每个AC_ID 的总值。如果不是增量存储机制,我可以使用

轻松实现这一点
SELECT AC_ID, ASAT, SUM(VAL) FROM POSITION
GROUP BY AC_ID, ASAT
ORDER BY ASAT DESC

我正在寻找的是能够实现上述目标的东西,但要考虑到桌面上的连接。如果我使用上面的方法,那么我只会得到在 ASAT 日期发生变化的任何东西的总数,而不是所有没有变化的现有值。

在上面的例子中应该等同于一个结果集

AC_ID | ASAT       | SUM(VAL)
    1 | 2016-01-01 |      300
    1 | 2016-01-02 |      350
    1 | 2016-01-03 |      250

这是另一个数据与输出的例子

AC_ID | POS_ID | ASAT       | VAL
    1 |      1 | 2016-01-01 | 100
    1 |      2 | 2016-01-01 | 200
    1 |      2 | 2016-01-02 | 250
    1 |      1 | 2016-01-03 | 0
    2 |      1 | 2016-01-02 | 500
    3 |      7 | 2016-01-02 | 1000
    3 |      7 | 2016-01-03 | 1000
    3 |     12 | 2016-01-03 | 5000
    2 |      1 | 2016-01-04 | 750

结果

AC_ID | ASAT       | SUM(VAL)
    1 | 2016-01-01 |      300
    1 | 2016-01-02 |      350
    1 | 2016-01-03 |      250
    2 | 2016-01-02 |      500
    2 | 2016-01-04 |      750
    3 | 2016-01-02 |     1000
    3 | 2016-01-03 |     6000

我改变了它的工作方式

虽然下面的答案有效,但它们的性能却非常糟糕(这不是作者的错!)为了得到可接受的结果(我需要亚秒级返回)我重构了表格以包含 end_date柱子。此列在每次插入时都会更新,以设置该行的生命周期。如果一行没有替代条目,则结束日期设置为 9999-12-31。我上面的例子变成了……

AC_ID | POS_ID | ASAT       | END_DATE   | VAL
    1 |      1 | 2016-01-01 | 2016-01-03 |  100
    1 |      2 | 2016-01-01 | 2016-01-02 |  200
    1 |      2 | 2016-01-02 | 9999-12-31 |  250
    1 |      1 | 2016-01-03 | 9999-12-31 |    0
    2 |      1 | 2016-01-02 | 2016-01-04 |  500
    3 |      7 | 2016-01-02 | 2016-01-03 | 1000
    3 |      7 | 2016-01-03 | 9999-12-31 | 1000
    3 |     12 | 2016-01-03 | 9999-12-31 | 5000
    2 |      1 | 2016-01-04 | 9999-12-31 |  750

然后我可以从接受的答案中删除第二个连接,并在内部连接中添加一个额外的子句。

SELECT
  p1.AC_ID, 
  p1.ASAT, 
  SUM(p2.VAL) as totalValue
FROM 
  (SELECT DISTINCT AC_ID, ASAT FROM position) p1
INNER JOIN position p2 ON
  p2.AC_ID    =  p1.AC_ID AND
  p2.ASAT     <= p1.ASAT AND
  p2.END_DATE >  p1.END_DATE
GROUP BY 
  p1.AC_ID,
  p1.ASAT;

【问题讨论】:

如果给定日期没有值怎么办?你还想要那个日期吗? 最好不要,即我只对表中列出的日期感兴趣,但如果为了实现这一点,我必须填写其间的每个日期,那么这应该没什么大不了的.日期间隔可能只有 1 或 2 天。 澄清一下,任何日期都会有一个值,因为它将是它之前的最新不同头寸的总和。例外情况是,如果日期在任何位置之前存在,在这种情况下结果将为 null/0 【参考方案1】:

这应该可以满足您的需求:

SELECT
    P1.ac_id,
    P1.asat,
    SUM(P2.val) AS total_value
FROM
    (SELECT DISTINCT P.ac_id, P.asat FROM dbo.Position P) P1
INNER JOIN dbo.Position P2 ON
    P2.ac_id = P1.ac_id AND
    P2.asat <= P1.asat
LEFT OUTER JOIN dbo.Position P3 ON
    P3.ac_id = P1.ac_id AND
    P3.pos_id = P2.pos_id AND
    P3.asat > P2.asat AND
    P3.asat <= P1.asat
WHERE
    P3.ac_id IS NULL
GROUP BY
    P1.ac_id,
    P1.asat

查询获取您所有的ac_id/asat 组合,然后抓取任何可能落入需要总计的行,最后使用LEFT OUTER JOIN 并检查NULL 以消除任何对于该特定 pos_id 而言不是最新的行。

【讨论】:

它确实有效。谢谢你,但在我接受这个之前,我想知道这里是否有可以优化的东西。当我运行 SELECT AC_ID, ASAT, SUM(VAL) FROM POSITION GROUP BY AC_ID, ASAT ORDER BY ASAT DESC 时,在 300k 行的表上执行需要 0.117 秒。新的 SQL 给了我正确的答案,但针对同一组数据需要超过 5 分钟才能完成。我确实有关于 ac_id、pos_id、asat 的索引。想知道是否需要 2 次全表扫描。 可能还有一些其他的索引可以优化得更好,但是您的业务规则的性质将需要多次表扫描。如果这是另一个 RDBMS,您可能会从窗口函数中获得更好的性能,但它们在 mysql 中不可用。你也可以尝试一些使用变量的技巧(这就是很多人在 MySQL 中模仿窗口函数的方式),但我现在没有可用于测试的服务器,也没有想到这一点。跨度> 两个答案都是正确的。这个是性能最好的。尽管非常糟糕。我决定重构以减少查询时间。已将其记录为对原始帖子的编辑。【参考方案2】:

这不是特别有效,但我认为它应该做你想要的:

SELECT aa.AC_ID, aa.ASAT,  SUM(p.VAL)
FROM (SELECT DISTINCT AC_ID, ASAT FROM POSITION
     ) aa JOIN
     POSITION P
     ON p.AC_ID = aa.AC_ID and p.ASAT <= aa.ASAT
WHERE P.ASAT = (SELECT MAX(P2.ASAT)
                FROM POSITION P2
                WHERE P.AC_ID  = P2.AC_ID AND
                      P.POS_ID = P2.POS_ID AND
                      P2.ASAT <= aa.ASAT
               )
GROUP BY aa.AC_ID, aa.ASAT;

【讨论】:

感谢您的起点。不幸的是,如果有多个 AC_ID,这将不起作用。它只是将所有内容聚合在一行中(并且需要很长时间!) @NathPapadacis 。 . .糟糕,应该有一个group by。这不会解决性能问题,但答案应该是正确的。 啊哈,差不多了。它只需要最后一项调整即可使其工作,即更改连接以检查ASAT 上的and p.ASAT <= aa.ASAT

以上是关于SQL Group By 每个日期的总和并使用最大日期的主要内容,如果未能解决你的问题,请参考以下文章

sql 语句,在group by 中选最大值max的问题

SqlAlchemy group_by 并返回最大日期

使用多个 WHERE 子句和 GROUP BY 销售人员访问 SQL、聚合总和

具有日期范围条件的 Group By 和 SUM 的 sql

在 SQL Server 中使用 group by 的列名总和?

sql 聚合函数和group by 联合使用