【问题标题】:How to efficiently cumulate counting records when you have multiple groupings in mysql?mysql中有多个分组时如何有效地累积计数记录?
【发布时间】:2021-05-27 08:20:06
【问题描述】:

假设你有这样一张桌子:

表用户:

  • userid(PK,用户的id)
  • regdate(日期时间、注册日期)
  • idprovince(地址省份的外部id)

如果我这样写一个查询:

选择 DATE_FORMAT(regdate,"%Y-%m-01") 作为 regmonth, 省内, 计数(用户 ID)为 num 从 用户 通过...分组 DATE_FORMAT(regdate,"%Y-%m-01"), idprovince

这将正确生成一个分组结果,该结果将显示在任何给定月份和省份注册了多少新用户。

假设现在我想要每个省在任何给定月份的累计用户数(任何给定月份和省份的值应该是该月和该省的新用户的总和以及之前所有月份的相同省),我应该如何构建高效的查询?

我尝试过使用这样的子查询:

SELECT
    DATE_FORMAT(regdate,"%Y-%m-01") as regmonth,
    idprovince                                 ,
    (SELECT
        COUNT(userid)
    FROM
        users AS counting_0_tbl
    WHERE DATE_FORMAT(counting_0_tbl.regdate,"%Y-%m-01")<=DATE_FORMAT(users.regdate,"%Y-%m-01")
        AND counting_0_tbl.idprovince                    = users.idprovince
    ) as num
FROM
    users
GROUP BY
    DATE_FORMAT(regdate,"%Y-%m-01"),
    idprovince

它运行正常,但需要 AGES 才能运行,比如在 70k 行表上需要 70 多秒。

知道如何提高效率吗?

我越来越多地考虑坚持基本查询并在第二阶段进行累积,在代码中......

我使用的是 Mysql 5.5,但如果有用我可以升级到 MySQL 8。

感谢您的帮助!

【问题讨论】:

  • 你能不能提供一个minimal reproducible example
  • 而且,如果可以的话,我认为你应该趁机升级
  • @Strawberry 请问你为什么说我应该升级?是不是 v8 在服务器上重了很多?
  • 更重?是的。重得多?不。优点远远大于缺点,但我确定我不需要在这里逐项列出。

标签: mysql performance grouping counting


【解决方案1】:

在 mysql 5.5 中,您使用用户定义的变量来汇总不同行的数字。

你必须保持列的顺序,否则算法将不起作用

CREATE tABLE users (userid int,regdate date,idprovince int )
INSERT INTO users VALUEs (1,'2020-01-21',1),(2,'2020-02-21',1),(3,'2020-03-21',1),
(4,'2020-01-21',2),(5,'2020-02-21',2),(6,'2020-03-21',2)
    SELECT 
        regmonth, 
        IF(@idprovince = idprovince,@num:=@num + `num` , @num:= `num`)  as num,
        @idprovince := idprovince as idprovince
    FROM
        (SELECT 
            DATE_FORMAT(regdate, '%Y-%m-01') AS regmonth,
                idprovince,
                COUNT(userid) AS num
        FROM
            users
        GROUP BY DATE_FORMAT(regdate, '%Y-%m-01') , idprovince
        ORDER BY idprovince , DATE_FORMAT(regdate, '%Y-%m-01')) t1,(SELECT @num:=0,@idprovince := 0) t2
正月 |编号 | idprovince :--------- | --: | ---------: 2020-01-01 | 1 | 1 2020-02-01 | 2 | 1 2020-03-01 | 3 | 1 2020-01-01 | 1 | 2 2020-02-01 | 2 | 2 2020-03-01 | 3 | 2

db小提琴here

【讨论】:

  • 谢谢!但是此刻我注意到,在测试您的解决方案时,顺便说一下,这与我的查询在一小部分时间内所做的完全一样,......它们都不能正常工作!在这些情况下会出现问题:假设在一个月内有一个用户从 A 省注册,然后下个月没有人从该省注册......我们的两个查询都出错了,因为在第二个月不插入行重复从A省上个月的总和......他们都跳过它!所以,你的答案是正确的,但我的问题是错误的! ;)
  • 在 sql 中可能会丢失日期,但是当您在此处查找时需要做很多工作。 .在 mysql 8 中,用 cte 构建一个包含所有日期的表并再次将其加入结果表会更简单,这样你一年中有一个月就像这里 stackoverflow.com/questions/6551179/…
【解决方案2】:

感谢@nbk 的输入,我设法创建了这个查询,它既快速又正确,并且基于每个月必须至少有一个用户注册的唯一假设;如果不是这种情况,则应研究另一种生成月份列表的方法。

选择 正月, 省内, 数, cumnum 从 (选择 正月, IF(@idprovince = idprovince,@cumnum:=@cumnum + `num` , @cumnum:= `num`) 作为 cumnum , @idprovince := idprovince 作为 idprovince, 数 从 ( 选择 users2.regmonth , users3.idprovince, 将(num,0) 合并为 num 从 (选择 date_format(regdate, "%Y-%m-01") 作为 regmonth 从 用户 通过...分组 date_format(regdate, "%Y-%m-01") ) 作为用户2 跨省加入 (选择 idprovince 从 用户 通过...分组 idprovince ) 作为用户3 左连接 (选择 省内, DATE_FORMAT(users.regdate,"%Y-%m-01") 作为 regmonth, 计数(id)作为数字 从 用户 通过...分组 省内, DATE_FORMAT(users.regdate,"%Y-%m-01") ) 作为 users_totals.idprovince=users3.idprovince 和 user_totals.regmonth=users2.regmonth 上的 users_totals 订购 users3.idprovince, 正月 ) 作为 t1 , (选择@cumnum:=0,@idprovince:= 0 ) 作为 t2 ) 作为 t3 订购方式 正月, idprovince

事实上,整个查询是以用户表中作为 regdate 的所有月份与用户表中的所有省 ID 之间的 CROSS JOIN(笛卡尔积)开始的。这样可以确保代表月份与现有省份 ID 的所有组合。

然后我们计算每个组中的正常计数,并将其连接到笛卡尔积,当连接失败时将合并添加到零。

然后使用@nbk 提出的方法生成运行总计,最后放置一个外部查询以恢复典型的基于时间的排序(已更改为正确汇总累计总计)。

这终于奏效了! :)

【讨论】:

    【解决方案3】:

    建立和维护每日小计的“汇总表”。每晚更新它,只添加前一天的新数据。然后,要获得“报告”,请将该汇总表中的总和相加。更多讨论:http://mysql.rjweb.org/doc.php/summarytables

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-24
      • 2021-08-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-14
      • 2019-12-01
      • 2022-08-04
      • 1970-01-01
      相关资源
      最近更新 更多