mysql中有多个分组时,如何高效累计统计记录?
How to efficiently cumulate counting records when you have multiple groupings in mysql?
假设你有一个像这样的 table:
table 用户:
- userid(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
这将正确地生成一个分组结果,该结果将显示在任何给定月份和省份注册了多少新用户。
假设现在我想要每个省份在任何给定月份的累计用户数(任何给定月份和省份的值应该是该月和省份的新用户以及之前所有月份的总和完全相同的省份),我应该如何构建高效的查询?
我试过这样使用子查询:
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
假设你有一个像这样的 table:
table 用户:
- userid(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
这将正确地生成一个分组结果,该结果将显示在任何给定月份和省份注册了多少新用户。
假设现在我想要每个省份在任何给定月份的累计用户数(任何给定月份和省份的值应该是该月和省份的新用户以及之前所有月份的总和完全相同的省份),我应该如何构建高效的查询?
我试过这样使用子查询:
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