【问题标题】:Calculate a running total in MySQL在 MySQL 中计算运行总计
【发布时间】:2010-10-14 11:06:18
【问题描述】:

我有这个 MySQL 查询:

SELECT DAYOFYEAR(`date`)  AS d, COUNT(*) 
FROM  `orders` 
WHERE  `hasPaid` > 0
GROUP  BY d
ORDER  BY d

返回如下内容:

d  | COUNT(*) |
20 |  5       |
21 |  7       |
22 | 12       |
23 |  4       |

我真正想要的是最后的另一列显示运行总数:

d  | COUNT(*) | ??? |
20 |  5       |   5 |
21 |  7       |  12 |
22 | 12       |  24 |
23 |  4       |  28 |

这可能吗?

【问题讨论】:

标签: mysql sql


【解决方案1】:

也许对您来说是一个更简单的解决方案,并且可以防止数据库执行大量查询。这只会执行一个查询,然后在一次通过中对结果进行一些数学运算。

SET @runtot:=0;
SELECT
   q1.d,
   q1.c,
   (@runtot := @runtot + q1.c) AS rt
FROM
   (SELECT
       DAYOFYEAR(`date`) AS d,
       COUNT(*) AS c
    FROM  `orders`
    WHERE  `hasPaid` > 0
    GROUP  BY d
    ORDER  BY d) AS q1

这将为您提供一个额外的 RT(运行总计)列。不要错过顶部的 SET 语句,以首先初始化运行总变量,否则您只会得到一列 NULL 值。

【讨论】:

  • 效果很好!查看上面的EXPLAIN 表明它比以前接受的答案更有效
  • 关键是使用子查询。这使得它在涉及多个表或聚合的复杂查询中可靠。
  • 对于那些想用 PHP 的基本 MySQL 函数做这样的事情的人,一定要单独运行第一行(但仍然在第二行之前)。
  • Ariel,请提供一个工作示例作为帖子的答案,而不是离开飞越。很想看看你的建议是如何运作的!
  • 值得一提的是,@rentot 变量可以在FROM 子句的SELECT 语句中设置,如下所示:... AS q1, (SELECT @runtot:=0) AS n。这可能会使 php 人的生活变得更加轻松,因为现在您只有一个语句查询。
【解决方案2】:
SELECT 
   DAYOFYEAR(O.`date`)  AS d, 
   COUNT(*),
   (select count(*) from `orders` 
       where  DAYOFYEAR(`date`) <= d and   `hasPaid` > 0)
FROM  
  `orders` as O
WHERE  
  O.`hasPaid` > 0
GROUP  BY d
ORDER  BY d

这将需要一些语法调整(我没有 MySQL 来测试它),但它向您展示了这个想法。子查询只需返回并添加您已经包含在外部查询中的所有新鲜内容,并且它必须对每一行都这样做。

查看this question,了解如何使用连接来完成相同的操作。

为了解决随着数据增长而导致性能下降的担忧:因为有最大值。一年 366 天,并且我假设您没有针对多年运行此查询,子查询将被评估多达 366 次。使用正确的日期索引和 hasPaid 标志,您会没事的。

【讨论】:

  • 请注意,这在大型、平均和一些小型数据库上会非常慢,因为它需要执行与结果中的行一样多的额外查询
  • 同意。我 +1 了这个答案,因为它很聪明,而且我们都在需要时使用过这样的解决方案,但我们也都知道这是有代价的。取决于您需要运行计数的位置。对于业务逻辑?然后也许在数据库中执行此操作。为了景色?在代码中执行。
【解决方案3】:

从 MySQL 8 开始,您将使用 window functions 进行此类查询:

SELECT dayofyear(`date`) AS d, count(*), sum(count(*)) OVER (ORDER BY dayofyear(`date`))
FROM `orders`
WHERE `hasPaid` > 0
GROUP BY d
ORDER BY d

在上述查询中,聚合函数count(*) 嵌套在窗口函数sum(..) OVER (..) 内,这可能是因为logical order of operations in SQL。如果这太令人困惑,您可以轻松地使用派生表或WITH clause 来更好地构建您的查询:

WITH daily (d, c) AS (
  SELECT dayofyear(`date`) AS d, count(*)
  FROM `orders`
  WHERE `hasPaid` > 0
  GROUP BY d
)
SELECT d, c, sum(c) OVER (ORDER BY d)
ORDER BY d

【讨论】:

    【解决方案4】:

    可以使用 MySQL 中的临时表计算运行余额。以下查询应该有效:

    CREATE TEMPORARY table orders_temp1 (SELECT id, DAYOFYEAR(`date`)  AS d, COUNT(*) as total FROM  `orders` WHERE  `hasPaid` > 0 GROUP BY d ORDER  BY d);
    CREATE TEMPORARY table orders_temp2 (SELECT * FROM orders_temp1);
    SELECT d, total, (SELECT SUM(t2.total) FROM orders_temp2 t2 WHERE t2.id<=t1.id) as running_total FROM orders_temp1 t1;
    

    临时表用于组织查询。请注意,临时表仅在连接到 MySQL 服务器期间存在

    上述查询使用子查询,它返回临时表中所有行的余额,包括当前行。余额分配到实际表中的当前行

    【讨论】:

    • 查询使用存储在内存中的临时表。它们比基于磁盘的表快得多。但是如果你的表有很多数据或者查询会被多个用户同时执行,那么使用临时表会使查询变慢。见:stackoverflow.com/questions/18865438/…
    【解决方案5】:

    我会说这是不可能的,每个结果行都应该是独立的。使用编程语言获取这些值

    【讨论】:

    • 鉴于关系数学的性质,以及您使用 group by 的事实,即使 mysql 有一些技巧可以使这成为可能,只用编程语言来做也不会那么复杂正如 Sergej 建议的那样。
    • 我不同意。从重用和维护的角度来看,在数据库和应用层之间拆分处理任务是有问题的。如果您想在不同的地方使用这些数据,可能在报表和屏幕上,您必须复制运行总计逻辑。
    • +1 你是对的:这在编程逻辑中总体上会更容易和更好 - 我试图看看是否有一些神奇的很棒的功能来做到这一点。
    • 运行总计列不是视图逻辑。它是嵌入在表中的数据。是的,SQL 是关系集逻辑,但几乎总是有序的。暗示这是不可能的是不正确的。建议其形式不佳需要讨论。
    • 这对于现代 DBMS(使用窗口函数)实际上很容易,它远非“不可能”
    猜你喜欢
    • 1970-01-01
    • 2017-10-04
    • 1970-01-01
    • 2010-10-26
    • 2023-03-23
    • 1970-01-01
    • 2012-07-03
    相关资源
    最近更新 更多