用单个输入数字逐行减法

Tii*_*mel 6 postgresql window-functions

我想从前一行减法结果中动态减去,作为输入,我想给出一个数字。

我有桌子 a

CREATE TABLE a (id int, code text, qty numeric);
Run Code Online (Sandbox Code Playgroud)

还有里面的数据

INSERT INTO a (id,code,qty)
(1,'test',5),
(2,'test',3),
(3,'test',10);
Run Code Online (Sandbox Code Playgroud)

1.sample - 当输入是16- 期望的结果是:

id | qty
--------------
1  | 0
2  | 0
3  | 2
Run Code Online (Sandbox Code Playgroud)

计算将是这样的

case when 16 > 5 then 16-5 else 5-16 end /* result 11, but displayed 0 */
case when 11 > 3 then 11-3 else 3-11 end /* result 8, but displayed 0 */
case when 8 > 10 then 8-10 else 10-8 end /* result 2 and displayed 2 */
Run Code Online (Sandbox Code Playgroud)

2.sample - 当输入是6- 期望的结果是:

id | qty
--------------
1  | 0
2  | 2
3  | 10 /* is optional to display */
Run Code Online (Sandbox Code Playgroud)

计算将是这样的

case when 6 > 5 then 6-5 else 5-6 end /* result 1, but displayed 0 */
case when 1 > 3 then 1-3 else 3-1 end /* result 2 and displayed 2 */
case when ... end /* result 10, since input is fulfilled */
Run Code Online (Sandbox Code Playgroud)

And*_*y M 9

从根本上说,这是一个运行总计问题:对于每一行,您都会得到qty的运行总计并从中减去一个固定值。更具体地说,这是关于您希望如何使用运行总计。在这种情况下,您希望根据先前计算的结果显示新的数量值。这里基本上有三种情况:

  1. 减法的结果是否定的。
  2. 减法的结果超过了当前行的qty
  3. 结果是不超过qty的非负值。

第一种情况意味着提供的固定值比用尽当前运行总数所必需的多——换句话说,它用尽了从开始到当前行(包括当前行)的所有qty值。因此,我们用 0 代替qty

在第二种情况下,我们只保留qty,因为现在运行总数超过了固定值,差值大于当前qty - 所以后者保持不变。

最后一种情况是固定值要么完全涵盖到目前为止的所有qty值,要么略小于总数但不少于:直到当前qty 的数量。在这种情况下,我们显示运行总计和固定值之间的差异。

上面的 SQL 是这样的:

SELECT
  id,
  CASE
    WHEN balance < 0   THEN 0
    WHEN balance > qty THEN qty
    ELSE balance
  END AS qty
FROM
  (
    SELECT
      id,
      qty,
      SUM(qty) OVER (ORDER BY id ASC) - @fixedSum AS balance
    FROM
      a
  ) AS derived
;
Run Code Online (Sandbox Code Playgroud)

其中@fixedSum是您要从运行总数中减去的固定值。

解决方案的核心部分是SUM(qty) OVER (ORDER BY id ASC)- 窗口聚合函数来获取qty的运行总数。

我在嵌套的 SELECT 中计算运行总数,因为减法的结果需要在 CASE 表达式中多次引用,并且您不能在同一级别引用计算列,您需要为此使用嵌套。嵌套可以采用派生表或公用表表达式 (CTE) 的形式。上述解决方案使用派生表。

不过,有一种方法可以避免嵌套,并使用函数 LEAST 和 GREATEST 在单个 SELECT 中完成所有计算:

SELECT
  id,
  qty,
  LEAST(
    qty,
    GREATEST(
      SUM(qty) OVER (ORDER BY id ASC) - @fixedSum,
      0
    )
  ) AS balance
FROM
  a
Run Code Online (Sandbox Code Playgroud)

但是,与使用 CASE 的方法相比,此方法要简单得多。在性能方面,它可能会起作用。

该方法是这样工作的。首先,您使用 GREATEST 函数选择减法结果和 0 之间的较大值。因此,就上面列出的情况而言,如果我们有情况 1,则 GREATEST 将产生 0。非零结果意味着我们有其他情况之一。

然后将获得的值与当前数量进行比较,这次使用 LEAST 函数选择两者中较小的一个。如果我们之前获得 0,则 LEAST 会保留它。如果我们有减法结果并且现在发现它大于qty,则 LEAST 函数会给我们qty(这意味着这是案例 3)。如果差异恰好小于qty,那就是 LEAST 将产生的结果(案例 2)。

  • 我没想到 LEAST/GREATEST 方法会明显更快,但最后我并不感到惊讶。至于逻辑上的差异,你的意思是你发现了使用这两种方法得到不同结果的情况?如果你能给我一个例子,我将不胜感激。我的意思是我意识到逻辑可能看起来不同,但我认为这两种变化最终都是等效的并给出相同的结果。 (3认同)