当mysql中有多个分组时,如何有效地累积计数记录?

问题描述

假设你有一个这样的表:

用户

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

如果我这样写查询

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

这将正确生成分组结果,显示在任何给定月份和省份注册新用户数量

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

我试过使用这样的子查询

9

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

知道如何提高效率吗?

我越来越想坚持基本查询并在第二阶段用代码进行累积...

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

感谢您的帮助!

解决方法

在 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',(3,'2020-03-21',(4,2),(5,(6,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
regmonth   | num | 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

dbfiddle here

,

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

SELECT
    regmonth,num,cumnum
FROM
    (SELECT
        regmonth,@cumnum:=@cumnum + `num`,@cumnum:= `num`) as cumnum,@idprovince := idprovince                                               as idprovince,num
    FROM
        ( select
            users2.regmonth,users3.idprovince,coalesce(num,0) as num
        FROM
            (select
                date_format(regdate,"%Y-%m-01") as regmonth
            from
                users
            group by
                date_format(regdate,"%Y-%m-01")
            ) as users2
        CROSS JOIN provinces
            (select
                idprovince
            from
                users
            group by
                idprovince
            ) as users3
        LEFT JOIN
            (SELECT
                idprovince,DATE_FORMAT(users.regdate,"%Y-%m-01") as regmonth,count(id)                                as num
            from
                users
            GROUP BY
                idprovince,"%Y-%m-01")
            ) as users_totals on users_totals.idprovince=users3.idprovince AND user_totals.regmonth=users2.regmonth
        order by
            users3.idprovince,regmonth
        ) as t1,(SELECT @cumnum:=0,@idprovince := 0
        ) as t2
    ) as t3
ORDER BY
    regmonth,idprovince

事实上,整个查询基于在用户表中作为regdate存在的所有月份和用户表中存在的所有省份ID之间的CROSS JOIN(笛卡尔积)开始。这可确保表示具有现有省份 ID 的月份的所有组合。

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

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

这终于奏效了! :)

,

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