【问题标题】:Efficient sliding window sum over a database table数据库表上的有效滑动窗口总和
【发布时间】:2012-02-09 12:29:50
【问题描述】:

数据库有一个transactions 表,其中包含以下列:account_iddatetransaction_value(有符号整数)。另一个表 (account_value) 存储每个帐户的当前总值,这是每个帐户所有 transaction_values 的总和。它使用transactions 表上的触发器进行更新(即,对transactions 的INSERT、UPDATE 和DELETE 触发触发器以更改account_value。)

一项新要求是仅在过去 365 天内计算帐户的总交易价值。只需要当前运行总计,而不需要以前的总计。将经常请求此值,几乎与account_value 一样频繁。

您将如何有效地实现这种“滑动窗口总和”?一个新的表是好的。有没有办法避免每次对一年的范围求和?

【问题讨论】:

  • 您是否保留了一年中所有不同日子的所有这些运行总计的记录?这样您就可以在单个报告中查看整个月的值?
  • 套用 Dems 的话说,您只是想要“从今天开始的最后 365 天”还是想要“从其他范围的每一天开始的最后 365 天”?
  • @gbn,今天才总结。
  • @Dems,只从今天开始总结,不需要每天的总和。
  • @user1147339:然后使用我的解决方案:不要预先计算它

标签: sql database-design sliding-window


【解决方案1】:

这可以通过标准的窗口函数来完成:

SELECT account_id,
       sum(transaction_value) over (partition by account_id order by date)
FROM transactions

over() 子句中的 order by 使总和成为“滑动总和”。

对于“仅过去 356 天”,您需要第二个查询来限制 WHERE 子句中的行。

以上适用于 PostgreSQL、Oracle、DB2 和(我认为)Teradata。 SQL Server 不支持窗口定义中的 order by(即将推出的 Denali 版本将 AFAIK)

【讨论】:

  • 仅供参考,关于 SQL Server 中具有聚合函数的 OVER 子句限制,您是正确的。只能使用 PARTITION BY
  • @gbn:你的意思是 SQL Server?
  • OP 目前似乎在跟踪一段时间内的运行总数。这似乎意味着正在运行的报告可能会一次提取多个不同日期的这些运行总数。比如查看一周、一个月甚至一年的运行变化。
【解决方案2】:

就这么简单?

SELECT
   SUM(transaction_value), account_id
FROM
   transactions t
WHERE
   -- SQL Server, Sybase       t.DATE >= DATEADD(year, -1, GETDATE())
   -- MySQL            t.DATE >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY
   account_id;

您可能希望在 SQL Server 中使用 DATE (MySQL) 或 this way 从日期表达式中删除时间组件

【讨论】:

  • Sybase ASE 也是第一个:P
  • @aF。我认为 OP 想要一份报告显示 year_to_date 每天的运行总数。因此,涵盖一年的报告将在不同的 365 天范围内有 365 个不同的运行总计,这是每个帐户。这确实是一个令人头疼的问题,并且如果经常更有效的话,基于触发器的当前值缓存。
【解决方案3】:

如果对事务表的查询比对事务表的插入更频繁,那么也许视图是要走的路?

【讨论】:

  • 还是折衷方案:物化视图或索引视图,具体取决于相关的 RDBMS?
  • 运行总计非常昂贵。这种基于触发器的方法将使用比直接查询更少的资源,即使读取频率比写入频率低 100 倍。
  • 但是基于触发器的方法不能保证总数是正确的。
【解决方案4】:

您将需要一个一次性脚本来为现有表填充每条现有记录的前一年的值 - 对于生成的每条记录,该脚本需要在上一年的整个时间内运行。

填充滚动年份列后,对上一年求和的一种替代方法是将每个新记录的值作为上一个记录的滚动年份值加上自上次更新以来的交易价值减去交易价值上次更新前一年到现在一年前。

我建议针对实际的测试数据尝试这两种方法,看看哪一种表现更好 - 我希望在数据相对稀疏的情况下,对全年进行求和至少会表现得一样好,而如果要获得数据,差异方法可能会更好每个帐户都经常更新。

【讨论】:

    【解决方案5】:

    我将避免使用任何实际的 SQL,因为它会根据您使用的 SQL 的种类而有很大差异。


    你说你有一个触发器来维持现有的运行总数。

    我认为它还会(或者可能是一个夜间过程)在account_value 表中创建新的每日记录。然后 INSERTs、UPDATEs 和 DELETEs 触发触发器以对现有的运行总计进行加减?

    您需要做的唯一更改是:
    - 添加一个新字段,“yearly_value”或其他内容
    - 以与现有字段相同的方式更新现有触发器
    - 使用 gbn 的答案类型来创建今天的记录(或无论您回溯多远)
    - 但初始化每个新的每日记录的方式略有不同...

    当您为新的一天插入新行时,应将其初始化为 yesterday's value - the value 365 days ago。之后,行为应该与您已经习惯的行为相同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-02
      • 2023-03-25
      • 2021-09-05
      • 2019-11-08
      相关资源
      最近更新 更多