mysql中有多个分组时,如何高效累计统计记录?

How to efficiently cumulate counting records when you have multiple groupings in mysql?

假设你有一个像这样的 table:

table 用户:

如果我这样写查询:

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

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

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

我试过这样使用子查询:

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 秒以上 table。

知道如何提高效率吗?

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

我正在使用 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',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
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

db<>fiddle here

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

SELECT
    regmonth      ,
    idprovince,
    num       ,
    cumnum
FROM
    (SELECT
        regmonth                                                                                 ,
        IF(@idprovince = idprovince,@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,
                DATE_FORMAT(users.regdate,"%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

事实上,整个查询是基于在用户 table 中作为注册日期存在的所有月份与用户 [=] 中存在的所有省份 ID 之间的交叉连接(笛卡尔积)开始21=]。这可确保表示月份与现有省份 ID 的所有组合。

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

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

这终于奏效了! :)

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