MySQL 中自定义排序的累计总和

Cumulative sum with custom sorting in MySQL

我有一个 table 看起来像这样

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;

本次查询return以下结果正确。

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 对它进行排序时,它 return 包含所有记录。

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

排序正确,但结果超出我的需要。

我想要什么

我要return下面的结果。

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

此外,数字 215 可以动态变化,因此行数 returned 可以根据该数字变化。我如何更改查询才能实现此目的?

编辑

对于我在结果集中实际想要的内容不清楚,我深表歉意。请让我澄清一下这个编辑。我不想要 运行-total 少于给定数量的记录。我想要记录,直到 运行-总数等于或超过给定的数量。

尝试使用限制 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

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

然后,运行 总数将在任何现代 RDBMS 中使用 window 函数完成。从版本 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;

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

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。