【问题标题】:Is there any way in SQL or function in MYSQL that sums up all the increments in a column?SQL或MYSQL中的函数有什么方法可以总结一列中的所有增量吗?
【发布时间】:2019-05-31 23:42:52
【问题描述】:

我想找到一种方法来总结列值中的所有增量。

我们为客户提供送货服务。客户可以随用随付,但如果他支付前期费用,他会得到更好的交易。有一张桌子,上面有客户在一段时间内的余额。所以我想把所有的增量加到余额中。我无法更改付款记录方式。

我已经编写了一个有效的存储过程,但速度很慢,所以我正在寻找替代方案。我认为,也许可以执行此任务的 sql 语句可以胜过具有循环的存储过程。

我的存储过程在给定日期范围内选择客户,并将结果插入到临时表 X 中。之后,它开始从 X 表中弹出行,将该行中的余额值与前一个进行比较行,并检测是否有增量。如果没有增量,则弹出另一行并执行相同的例程,如果有增量,则计算该行与上一行的差,并将结果插入另一个临时表Y中。

当没有剩余行时,存储过程在临时表 Y 中执行求和,因此,您可以知道客户“重新填充”了多少余额。

这是表 X 的示例,以及预期的结果:

DATE        BALANCE
----        -------
2019-02-01  200
2019-02-02  195    //from 200 to 195 there is a decrement, so it doesn't matter
2019-02-03  180
2019-02-04  150
2019-02-05  175    //there is an increment from 150 to 175, it's 25 that must be inserted in the temp table
2019-02-06  140
2019-02-07  180    //there is another increment, from 140 to 180, it's 40

所以生成的临时表 Y 必须是这样的:

REFILL
------
25
40

预期的结果是 65。我的存储过程返回这个值,但正如我所说,它有点慢(处理 3900 行大约需要 22 秒,大约相当于 3 天),我认为是因为循环。我想探索另一种选择。因为这里没有提到的一些细节,对于单个客户,我每天可以有 1300 行(示例以天为单位给出,但我有按分钟计算的行)。我的表被索引了,我认为是正确的。我无法发布我的存储过程,但它按描述工作(我知道“魔鬼在细节中”)。因此,任何建议都会受到赞赏。

【问题讨论】:

  • 你用的是什么版本的 MySQL?
  • 嗨,我使用的是 5.7.26。

标签: mysql sql


【解决方案1】:

使用用户定义的变量来保存上一行的余额,然后从当前行的余额中减去它。

SELECT SUM(refill) AS total_refill
FROM (
  SELECT GREATEST(0, balance - @prev_balance) AS refill, @prev_balance := balance
  FROM (
    SELECT balance
    FROM tableX
    ORDER BY date) AS t
  CROSS JOIN (SELECT @prev_balance := NULL) AS ars
) AS t

【讨论】:

  • “涉及用户变量的表达式的计算顺序未定义。” (来自文档 dev.mysql.com/doc/refman/8.0/en/user-variables.html),所以这不能保证有效。
  • 谢谢,我周末不在家,但我会发布我的结果。感谢您的所有帮助。
  • 嗨@Barmar。首先,抱歉耽搁了,我被分配到另一个项目,最近又回到了上一个项目。我确实尝试了您的解决方案,并且性能明显更好。我知道我的存储过程中的一些 SELECT 是存储过程处理时间的原因。因此,使用您的解决方案将执行时间缩短了 10 倍。我比较了结果,它们始终如一。谢谢;)
【解决方案2】:

有一种众所周知的机制来处理这些问题:在字段中使用变量。

SELECT @result:=0;
SELECT @lastbalance:=9999999999; -- whatever value is sure to be highe than any real balance
SELECT SUM(increments) AS total FROM (
  SELECT
    IF(balance>@lastbalance, balance-@lastbalance, 0) AS increments,
    @lastbalance:=balance AS ignore
  FROM X -- insert real table name here
  WHERE
    -- insert selector here
  ORDER BY
    -- insert real chronological sorter here
) AS baseview;

【讨论】:

  • 。 .我认为众所周知“涉及用户变量的表达式的求值顺序是未定义的”。 (来自文档dev.mysql.com/doc/refman/8.0/en/user-variables.html 并且在早期版本中也是如此),因此此方法被明确记录为不保证工作
  • 评估的顺序确实是未定义的,这与定义明确的评估与分配的顺序相反:分配将发生在评估之后。这意味着,ignore 的值将被...忽略。
  • 我也会尝试你的建议。像许多人一样,我不喜欢使用硬编码的东西。但如果它有效,它很简单,并且涵盖了我可以面对的所有用例,那么我已经准备好使用它了!
  • 我尝试了这篇文章中的第一个解决方案,它成功了!它和你的很相似。谢谢。
【解决方案3】:

在 MySQL 8+ 中使用 lag()

select sum(balance - prev_balance) as refills
from (select t.*, lag(balance) over (order by date) prev_balance
      from t
     ) t
where balance > prev_balance;

在旧版本的 MySQL 中,这很棘手。如果值是连续日期,那么简单的JOIN 就可以了:

select sum(t.balance - tprev.balance) as refills
from t join
     t tprev
     on tprev.date = t.date - 1
where t.balance > tprev.balance;

情况可能并非如此。然后下一个最好的方法是变量。但是你必须非常小心。 MySQL 没有在SELECT 中声明表达式的求值顺序。正如documentation 解释的那样:

涉及用户变量的表达式的计算顺序未定义。例如,不能保证 SELECT @a, @a:=@a+1 先计算 @a 然后再执行赋值。

变量需要赋值在同一个表达式中使用:

select sum(balance - prev_balance) as refills
from (select t.*,
             (case when (@temp_prevb := @prevb) = NULL  -- intentionally false
                   then -1 
                   when (@prevb := balance) 
                   then @temp_prevb
              end) as prev_balance
      from (select t.* from t order by date) t cross join
           (select @prevb := NULL) params
     ) t
where balance > prev_balance;

最后一个方法是关联子查询:

select sum(balance - prev_balance) as refills
from (select t.*,
             (select t2.balance
              from t t2
              where t2.date < t.date
              order by t2.date desc
             ) as prev_balance
      from t
     ) t
where balance > prev_balance;

【讨论】:

  • 哇,很好解释。我会试一试并及时通知您。
  • 我尝试了与您类似的@Barmar 解决方案。我无法尝试使用 MySql 8+,因为我有 5.7.26 此外,日期不是按天递增的,它们通常按分钟递增,但并非总是如此。您建议的最后一个解决方案类似于我在 Barmar 建议中使用的解决方案。谢谢!
猜你喜欢
  • 1970-01-01
  • 2012-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-25
  • 2013-06-17
  • 2020-05-31
相关资源
最近更新 更多