【问题标题】:Running total of positive and negative numbers where the sum cannot go below zero总和不能低于零的正数和负数的总和
【发布时间】:2020-01-23 17:18:23
【问题描述】:

这是一个 SQL 问题。

我有一列可以是正数或负数的数字,我正试图找出一种方法来获得该列的运行总和,但总和不能低于零。

Date       | Number | Desired | Actual
2020-01-01 |    8   |   8     |    8
2020-01-02 |   11   |  19     |   19
2020-01-03 |   30   |  49     |   49
2020-01-04 |  -10   |  39     |   39
2020-01-05 |  -12   |  27     |   27
2020-01-06 |   -9   |  18     |   18
2020-01-07 |  -26   |   0     |   -8
2020-01-08 |    5   |   5     |   -3
2020-01-09 |  -23   |   0     |  -26
2020-01-10 |   12   |  12     |  -14
2020-01-11 |   14   |  26     |    0

对此我尝试了许多不同的窗口函数,但没有找到防止运行总数变为负数的方法。

任何帮助将不胜感激。


编辑 - 添加日期列来指示排序

【问题讨论】:

  • 我假设您有一个指定排序的列,因为 SQL 表代表 无序 集。
  • 我添加了一个日期列以明确表示,是的,排序将始终相同。

标签: sql snowflake-cloud-data-platform


【解决方案1】:

不幸的是,如果不逐一浏览记录,就无法做到这一点。反过来,这需要递归 CTE 之类的东西。

with t as (
      select t.*, row_number() over (order by date) as seqnum
      from mytable t
     ),
     cte as (
      select NULL as number, 0 as desired, 0 as seqnum
      union all
      select t.number,
             (case when cte.desired + t.number < 0 then 0
                   else cte.desired + t.number
              end),
             cte.seqnum + 1
      from cte join
           t
           on t.seqnum = cte.seqnum + 1
     )
select cte.*
from cte
where cte.number is not null;

仅当您的数据相当少时,我才会推荐这种方法。但话又说回来,如果你必须这样做,除了逐行浏览表格之外,没有太多选择。

Here 是一个 dbfiddle(使用 Postgres)。

【讨论】:

  • 嗯...是的,这实际上可能对我的问题有用。它的记录数量相当有限,因为它是在聚合查询的结果上运行的。我去测试一下,谢谢。
  • 其实这里有几个问题……一个很简单,“cte as”后面少了一个括号。第二个是“cte”中没有“number”字段,所以查询会失败。这可以通过将“select NULL, 0 as desired”更改为“select NULL as number, 0 as required”来解决。第三个问题比较难:使用的join(“join t.seqnum on cte.seqnum + 1”)引用了字段“cte.seqnum”,除了……cte中没有seqnum字段。实际上没有第三个字段。
  • @Momus 。 . .感谢您的反馈。它是固定的。我添加了一个 dbfiddle 以表明它可以工作。
  • 哇。这是一个相当聪明的人。我实际上将 seqnum 字段添加到查询中,“选择 NULL 作为数字,0 作为所需,0 作为 seqnum”并将“cte.seqnum”添加到 cte 查询,但我没有弄清楚它需要是“cte .seqnum + 1"。谢谢,将此标记为选定的答案。
【解决方案2】:

您可以使用 CASE 运算符和 SIGN 函数来执行此操作……

CASE SIGN(my computed expression) WHEN -1 THEN 0 ELSE my computed expression END AS Actual

【讨论】:

  • 我很确定这个结果将是 [8,19,49,39,27,18,0,0,0,0,0] 因为总和聚合仍在计算负数,但会将它们显示为零。最后 5 个数字需要是 [...0,5,0,12,26] - 例如,它需要显示 0 + 5 的 5 的结果,而不是 -8 + 的 -3(替换为 0) 5.
  • 它取决于“我的计算表达式”中的表达式。如果它是一个窗口函数,它应该可以工作
  • 我绝对邀请您尝试一下,例如使用dbfiddle.uk,但如果不使用递归或 UDF,我看不到任何获得所需结果的方法。问题是窗口函数会修改输出,但输入不受影响,因此 (10, -20, 5, 10) 的“总和”将计算为 (+10=10, 10-20=-10 , 10-20+5=-5, 10-20+5+10=5) 它将显示为 (10, 0, 0, 5) 但正确的输出应该是 (10, 0, 5, 15)
【解决方案3】:

这可以通过USER DEFINE TABLE FUNCTION 来“管理”你想要携带的状态

CREATE OR REPLACE FUNCTION non_neg_sum(val float) RETURNS TABLE (out_sum float)
LANGUAGE JAVASCRIPT AS
'{
  processRow: function (row, rowWriter) {
    this.sum += row.VAL;
    if(this.sum < 0)
        this.sum = 0;
    rowWriter.writeRow({OUT_SUM: this.sum})
  },
  initialize: function() {
    this.sum = 0;
  }
}';

并像这样使用:

WITH input AS
(
    SELECT *
    FROM VALUES ('2020-01-01', 8, 8),
        ('2020-01-02', 11, 19 ),
        ('2020-01-03', 30, 49 ),
        ('2020-01-04',-10, 39 ),
        ('2020-01-05',-12, 27 ),
        ('2020-01-06', -9, 18 ),
        ('2020-01-07',-26,  0 ),
        ('2020-01-08',  5,  5 ),
        ('2020-01-09',-23,  0 ),
        ('2020-01-10', 12, 12 ),
        ('2020-01-11', 14, 26 ) d(day,num,wanted)
)
SELECT d.*
    ,sum(d.num)over(order by day) AS simple_sum
    ,j.*
FROM input AS d,
  TABLE(non_neg_sum(d.num::float) OVER (ORDER BY d.day)) j
ORDER BY day
;

给出结果:

DAY          NUM     WANTED    SIMPLE_SUM    OUT_SUM
2020-01-01   8       8         8             8
2020-01-02   11      19        19            19
2020-01-03   30      49        49            49
2020-01-04   -10     39        39            39
2020-01-05   -12     27        27            27
2020-01-06   -9      18        18            18
2020-01-07   -26     0         -8            0
2020-01-08   5       5         -3            5
2020-01-09   -23     0         -26           0
2020-01-10   12      12        -14           12
2020-01-11   14      26        0             26

【讨论】:

  • 很好的答案。我给了你一个赞成票,甚至向客户建议了这个选项,但最后我不得不给 Gordon Linoff 打勾,因为他的回答不需要 UDF。感谢您的帮助。
  • 很公平,如果您想要一个可以在只读帐户中使用的答案
【解决方案4】:

另一个UDF解决方案:

select d, x, conditional_sum(x) from values 
  ('2020-01-01',   8), 
  ('2020-01-02',  11), 
  ('2020-01-03',  30), 
  ('2020-01-04', -10), 
  ('2020-01-05', -12), 
  ('2020-01-06',  -9), 
  ('2020-01-07', -26), 
  ('2020-01-08',   5), 
  ('2020-01-09', -23), 
  ('2020-01-10',  12), 
  ('2020-01-11',  14)
  t(d,x)
order by d;

其中conditional_sum 定义为:

create or replace function conditional_sum(X float) 
returns float 
language javascript 
volatile
as 
$$
    if (!('sum' in this)) this.sum = 0
    return this.sum = (X+this.sum)<0 ? 0 : this.sum+X 
$$;

【讨论】:

  • 很好,我在想它应该可以使用普通的 UDF 函数。这个版本的缺点是如果你的数据是分区的或者如果你有足够的行,UDF 没有运行在相同的核心上,所以结果会是错误的。
  • 这是一个很好的答案,我给了你一个赞成票,但我不得不给 Gordon Linoff 打勾,因为他的答案不需要 UDF。
【解决方案5】:

演示:

WITH input AS
(   SELECT *
    FROM (VALUES 
        ('2020-01-01', 8, 8),
        ('2020-01-02', 11, 19 ),
        ('2020-01-03', 30, 49 ),
        ('2020-01-04',-10, 39 ),
        ('2020-01-05',-12, 27 ),
        ('2020-01-06', -9, 18 ),
        ('2020-01-07',-26,  0 ),
        ('2020-01-08',  5,  5 ),
        ('2020-01-09',-23,  0 ),
        ('2020-01-10', 12, 12 ),
        ('2020-01-11', 14, 26 ),
        ('2020-01-12', 3,  26 )) AS d (day,num,wanted)
)
SELECT *, sum(num)over(order by day) AS CUM_SUM,
       CASE SIGN(sum(num)over(order by day)) 
          WHEN -1 THEN 0 
          ELSE sum(num)over(order by day) 
       END AS Actual
FROM   input 
ORDER BY day;

返回:

day                num      wanted     CUM_SUM      Actual
---------- ----------- ----------- ----------- -----------
2020-01-01           8           8           8           8
2020-01-02          11          19          19          19
2020-01-03          30          49          49          49
2020-01-04         -10          39          39          39
2020-01-05         -12          27          27          27
2020-01-06          -9          18          18          18
2020-01-07         -26           0          -8           0
2020-01-08           5           5          -3           0
2020-01-09         -23           0         -26           0
2020-01-10          12          12         -14           0
2020-01-11          14          26           0           0
2020-01-12           3          26           3           3

我在您的测试值中再添加一行……以证明最终的条件总和为 3

【讨论】:

  • 这不是所需的输出。 sum 函数的输出需要与“wanted”列中的数字相匹配。此外,您在“wanted”列中添加的最后一个值应该是 29。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-15
  • 1970-01-01
  • 1970-01-01
  • 2016-04-23
  • 2021-03-27
  • 1970-01-01
相关资源
最近更新 更多