【问题标题】:Cumulative sum with custom sorting in MySQLMySQL中自定义排序的累积和
【发布时间】:2019-11-07 09:18:29
【问题描述】:

我有一张像这样的表

id   remaining   expiry_date
1    200         2019-11-15
2     10         2019-11-23
3     10         2019-11-16
4     10         2019-11-16
5      7         2019-11-16

我想获取运行总数为 215 且按 expiry_date 排序的 ascending 顺序的结果。

到目前为止我能做到什么?

SELECT *, @sum := (@sum + remaining) AS csum 
FROM tickets 
JOIN (SELECT @sum := 0) r 
WHERE @sum < 215 
ORDER BY id;

此查询返回以下正确的结果。

id   remaining   expiry_date   csum
1    200         2019-11-15    200
2     10         2019-11-23    210
3     10         2019-11-16    220

但是当我尝试使用expiry_date 对其进行排序时,它会返回所有记录。

SELECT *, @sum := (@sum + remaining) AS csum 
FROM tickets 
JOIN (SELECT @sum := 0) r 
WHERE @sum < 215 
ORDER BY expiry_date;

结果:

id   remaining   expiry_date   csum
1    200         2019-11-15    200
3    10          2019-11-16    210
4    10          2019-11-16    220
5    7           2019-11-16    227
2    10          2019-11-23    237

排序是正确的,但结果比我需要的要多。

我想要什么

我想返回以下结果。

id   remaining   expiry_date   csum
1    200         2019-11-15    200
3    10          2019-11-16    210
4    10          2019-11-16    220

此外,数字 215 可以动态变化,因此返回的行数可以根据该数字而变化。如何更改查询以便实现此目的?

编辑

对于我不清楚我在结果集中真正想要的内容,我深表歉意。请让我对此编辑进行澄清。我不希望运行总计小于给定数量的记录。我想要记录,直到运行总计等于或超过给定数量。

【问题讨论】:

  • 您似乎想要“小于或等于 215 的(最高或全部)运行总数” - 除了 220 大于 215 !?!?!
  • @Strawberry 我想要总和的记录,直到它通过总和 215,因此总和为 220 的记录。
  • 我对 MySQL 中的变量没有经验。我想ORDER BY 不知何故迟到了。不知道。你的 MySQL 版本是多少?从 MySQL 8 开始,您将像在其他现代 DBMS 中一样使用窗口函数,而不是使用变量。
  • 那为什么不这么说!?!?

标签: mysql sql cumulative-sum


【解决方案1】:

首先,每个日期有多个条目。因此,仅靠日期不足以获得稳定的排序顺序。我建议ORDER BY expiry_date, id 弄清楚这一点。

然后,将在任何现代 RDBMS 中使用窗口函数完成运行总计。从版本 8 开始,它们在 MySQL 中可用。

select id, remaining, expiry_date, csum
from
(
  select
    id, remaining, expiry_date, 
    sum(remaining) over (order by expiry_date, id) as csum,
    sum(remaining) over (order by expiry_date, id 
                         rows between unbounded preceding and 1 preceding) as lag_csum
  from tickets
) summed
where coalesce(lag_csum, 0) < 215
order by expiry_date, id;

如果窗口函数不可用,您可以改用相关聚合子查询。这可能要慢得多,但应该也能正常工作。

select *
from
(
  select
    id, remaining, expiry_date,
    (
      select sum(remaining)
      from tickets t2
      where t2.expiry_date < t1.expiry_date
        or (t2.expiry_date = t1.expiry_date and t2.id <= t1.id)
    ) as csum,
    (
      select sum(remaining)
      from tickets t2
      where t2.expiry_date < t1.expiry_date
        or (t2.expiry_date = t1.expiry_date and t2.id < t1.id)
    ) as lag_csum
  from tickets t1
) summed
where coalesce(lag_csum, 0) < 215
order by expiry_date, id;

这两个查询都是标准 SQL,因此不限于 MySQL。

【讨论】:

  • 它似乎有效,但你在哪里放置额外的条件检查。比如如果我只需要获取尚未过期的记录呢?
  • 当然是在 where 子句中。如果必须考虑这些行以获得正确的运行总计,则必须在获取它们后应用条件,即在主查询中。如果为了获得正确的运行总计而不必考虑这些行,那么您必须在获取它们之前应用条件,即在第一个查询的子查询或第二个查询的最里面的子查询中。
【解决方案2】:

尝试使用限制 3 并将此结果作为新订单的子查询:

SELECT * 
FROM
    (SELECT *, @sum := (@sum + remaining) AS csum 
     FROM tickets 
     JOIN (SELECT @sum := 0) r 
     WHERE @sum < 215 
     ORDER BY id 
     LIMIT 3) t 
ORDER BY expiry_date

或者根据您更新的问题,您可能只需要按日期订购的最后 3 个

SELECT *, @sum := (@sum + remaining) AS csum 
FROM tickets 
JOIN (SELECT @sum := 0) r 
WHERE @sum < 215 
ORDER BY expiry_date
LIMIT 3;

否则,如果您不想使用限制但您想过滤 csum 的结果,那么您可以尝试使用您的查询作为子查询并过滤您想要的值,例如:225

SELECT * 
FROM
    (SELECT t.*, @sum := (@sum + t.remaining) AS csum 
     FROM tickets t
     JOIN (SELECT @sum := 0) r 
     ORDER BY expiry_date ) t1 
WHERE t1.csum < 225

检查

SELECT * 
FROM
    (SELECT t.*, @sum := (@sum + t.remaining) AS csum 
     FROM 
         (SELECT 1 id, 200 remaining, '2019-11-15' expiry_date
          UNION ALL
          SELECT 2, 10, '2019-11-23'
          UNION ALL
          SELECT 3, 10, '2019-11-16'
          UNION ALL
          SELECT 4, 10, '2019-11-16'
          UNION ALL 
          SELECT 5, 7, '2019-11-16') t
     JOIN (SELECT @sum := 0) r 
     ORDER BY expiry_date ) t1 
WHERE t1.csum < 225

【讨论】:

  • 它从原始表中按顺序(id)1, 3, 2 返回结果,但我希望它按顺序1, 3, 4。而且我真的不想使用限制,因为数字 215 可以动态更改,因此返回的结果可以是任意数量的行。
  • 正如我在评论中提到的,我真的不想使用限制,因为条件查询可以动态更改。使用 limit 只是解决了这种特殊情况,如果我想将 215 更改为 225 那么它将返回 3 条记录,但我也想要第 4 条和第 5 条记录。而且我不能动态限制记录。
  • 这也没有给我预期的结果。
  • 答案已更新 .. 带有用于检查有效结果的样本
  • 我也尝试过这些类型的查询,但它总是会给我少一条记录。无论如何感谢您的尝试。我找到了让它发挥作用的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-17
  • 1970-01-01
  • 2022-08-18
  • 2022-11-02
  • 2021-06-02
  • 2013-04-09
  • 1970-01-01
相关资源
最近更新 更多