【问题标题】:How do I calculate a running SUM on a SQLite query?如何计算 SQLite 查询的运行 SUM?
【发布时间】:2011-08-02 04:04:24
【问题描述】:

如何获得一列是另一列之前所有值的总和?

【问题讨论】:

  • 请提供示例数据和预期输出。

标签: sql sqlite aggregate-functions


【解决方案1】:

从 SQLite 3.25.0 开始,自 2018 年 9 月 15 日起,支持 window functions 及其关键字 OVER。您的问题的答案现在很简单:

SELECT Country, Gdp, SUM(Gdp) OVER (ROWS UNBOUNDED PRECEDING)
FROM CountryGdp;

这是执行您请求的最小查询,但它没有定义任何顺序,因此这是一种更合适的方法。

SELECT
    Country,
    Gdp,
    SUM(Gdp) OVER (
        ORDER BY Country -- Window ordering (not necessarily the same as result ordering!)
        ROWS BETWEEN -- Window for the SUM includes these rows:
            UNBOUNDED PRECEDING -- all rows before current one in window ordering
            AND CURRENT ROW -- up to and including current row.
        ) AS RunningTotal
FROM CountryGdp
ORDER BY Country;

无论如何,查询应该在 O(N) 时间内运行。

【讨论】:

  • 这真的在 O(N) 中运行吗?根据我的经验,它仍然是 O(N^2)
  • 回到我写答案的时候,我想我检查了查询计划操作码输出。它使用协程来获得 O(N) 运行时。我怀疑新版本的情况会变得更糟,但我想这是可能的。此外,谁知道它可以为稍微改变的顺序、索引或其他什么产生什么输出。 SQL 并没有真正承诺任何性能特征 AFAIK。
  • 换句话说:好点子,你应该自己检查它是否对你的用例很重要。
【解决方案2】:

您可以通过将表格与自身连接起来(执行所谓的笛卡尔或cross join)来实现。请参阅以下示例。

SELECT a.name, a.gdppc, SUM(b.gdppc)
FROM gdppc AS a, gdppc AS b WHERE b.gdppc <= a.gdppc 
GROUP BY b.id ORDER BY a.gdppc;

给定一张包含国家及其人均 GDP 的表格,它将为您提供 GDP 的总和。

Democratic Republic of Congo|329.645|329.645
Zimbabwe|370.465|700.11
Liberia|385.417|1085.527
Burundi|399.657|1485.184
Eritrea|678.954|2164.138
Niger|711.877|2876.015
Central African Republic|743.945|3619.96
Sierra Leone|781.594|4401.554
Togo|833.803|5235.357
Malawi|867.063|6102.42
Mozambique|932.511|7034.931
...

请注意,这可能是一个非常耗费资源的操作,因为如果一个表有 N 个元素,它将创建一个具有 N*N 个元素的临时表。我不会在大桌子上执行它。

【讨论】:

  • 谢谢。在您的示例中,TABLE 名称是什么?
  • @Diomidis Spinellis:实际上这不是交叉连接,而是triangular join
  • 对不起,我不明白。在 FROM 语句中我应该在哪里写我的 TABLE 名称?
  • @Hugo gdppc 是表名
  • @Andriy M 对! WHERE 子句使交叉连接成为三角形连接。
【解决方案3】:

Diomidis Spinellis 这样的交叉连接解决方​​案建议花费 O(N^2) 时间。如果您能忍受复杂的代码,递归 CTE 可以更快地工作。

这会产生与他相同的输出。

WITH RECURSIVE running(id, name, gdppc, rt) AS (
    SELECT row1._rowid_, row1.name, row1.gdppc, COALESCE(row1.gdppc,0)
    FROM gdppc AS row1
    WHERE row1._rowid_ = (
        SELECT a._rowid_
        FROM gdppc AS a
        ORDER BY a.gdppc, a.name, a._rowid_
        LIMIT 1)
    UNION ALL
    SELECT row_n._rowid_, row_n.name, row_n.gdppc, COALESCE(row_n.gdppc,0)+running.rt
    FROM gdppc AS row_n INNER JOIN running
    ON row_n._rowid_ = (
        SELECT a._rowid_
        FROM gdppc AS a
        WHERE (a.gdppc, a.name, a._rowid_) > (running.gdppc, running.name, running.id)
        ORDER BY a.gdppc, a.name, a._rowid_
        LIMIT 1))
SELECT running.name, running.gdppc, running.rt
FROM running;

排序和比较处理重复,COALESCE 可以忽略 NULL。

如果你有一个好的索引,这应该是 O(N log N)。由于 SQLite 不支持游标,如果不依赖外部应用程序,可能不存在 O(N) 解决方案。

【讨论】:

  • 哎呀...我只想知道一件事,简单地检索整个集合然后在实际的编程代码中进行分组和计算不是更好吗?为什么要为这种难以理解的语法烦恼。
  • 我无法对此进行改进:窗口函数会不必要地重新计算大的聚合值,但递归 CTE 可以避免这种情况。谢谢你的回答。
【解决方案4】:

如果您的 SQLite 版本不支持 OVER 这是对 group_concat 行字符串使用递归的另一种方法。
在 SQLite 版本 3.22.0 2018-01-22 18:45:57 group_concat 按数据库顺序返回行。创建一个公用表表达式,并按照示例中的表 work1 对其进行排序。

/* cumulative running total using group_concat and recursion
   adapted from https://blog.expensify.com/2015/09/25/the-simplest-sqlite-common-table-expression-tutorial/
*/

WITH RECURSIVE work2 AS (
  SELECT NULL AS name, NULL AS gdppc, 0 AS cum, (select group_concat(name) from work1) AS gcname, (select group_concat(gdppc) from work1) AS gcgdppc 
  UNION
        SELECT
            CASE
                WHEN INSTR(gcname, ',' )>0 THEN 
                    SUBSTR(gcname, 0, INSTR(gcname,','))
                ELSE
                    gcname
            END,
            CASE
                WHEN INSTR(gcgdppc, ',' )>0 THEN 
                    SUBSTR(gcgdppc, 0, INSTR(gcgdppc,','))
                ELSE
                    gcgdppc
            END,
            CASE
                WHEN INSTR(gcgdppc, ',' )>0 THEN 
                    cum + SUBSTR(gcgdppc, 0, INSTR(gcgdppc,','))
                ELSE
                    cum + gcgdppc
            END,
            CASE
                WHEN INSTR( gcname, ',' )>0 THEN 
                    SUBSTR( gcname, INSTR( gcname, ',' )+1 )
                ELSE
                    NULL
            END,
            CASE
                WHEN INSTR(gcgdppc, ',' )>0 THEN 
                    SUBSTR( gcgdppc, INSTR( gcgdppc, ',' )+1 )
                ELSE
                    NULL
            END
        FROM work2
        WHERE gcgdppc IS NOT NULL

  ), 
/* SQLite version 3.22.0 2018-01-22 18:45:57
   group_concat ignores ORDER BY when specified against the base table
   but does appear to follow the order of a common table expression 
*/
  work1 AS (select * from gdppc order by gdppc),

  gdppc AS (SELECT 'Burundi' AS name,399.657 AS gdppc
            UNION
            SELECT 'Democratic Republic of Congo', 329.645
            UNION
            SELECT 'Liberia',385.417
            UNION
            SELECT 'Zimbabwe',370.465)

select name,gdppc,cum from work2 where name IS NOT NULL;
/* result
Democratic Republic of Congo|329.645|329.645
Zimbabwe|370.465|700.11
Liberia|385.417|1085.527
Burundi|399.657|1485.184
*/

【讨论】:

    【解决方案5】:

    您必须在您想要的字段中进行求和...。查询取决于您使用的数据库,Oracle 允许您这样做:

    select id, value, sum(value) as partial_sum over (order by id) from table
    

    【讨论】:

      猜你喜欢
      • 2011-07-09
      • 2021-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多