SQL计算一段时间内的累积值
Posted
技术标签:
【中文标题】SQL计算一段时间内的累积值【英文标题】:SQL Calculate cumulative value over a period 【发布时间】:2020-04-11 03:17:49 【问题描述】:我正在尝试计算自 2020 年 1 月 1 日以来随时间推移的累计收入。我有以下架构的用户级收入数据
create table revenue
(
game_id varchar(255),
user_id varchar(255),
amount int,
activity_date varchar(255)
);
insert into revenue
(game_id, user_id, amount, activity_date)
values
('Racing', 'ABC123', 5, '2020-01-01'),
('Racing', 'ABC123', 1, '2020-01-04'),
('Racing', 'CDE123', 1, '2020-01-04'),
('DH', 'CDE123', 100, '2020-01-03'),
('DH', 'CDE456', 10, '2020-01-02'),
('DH', 'CDE789', 5, '2020-01-02'),
('DH', 'CDE456', 1, '2020-01-03'),
('DH', 'CDE456', 1, '2020-01-03');
预期输出
Game Age Cum_rev Total_unique_payers_per_game
Racing 0 5 2
Racing 1 5 2
Racing 2 5 2
Racing 3 7 2
DH 0 0 3
DH 1 15 3
DH 2 117 3
DH 3 117 3
年龄计算为交易日期与 2020-01-01 之间的差异。 我正在使用以下逻辑
SELECT game_id, DATEDIFF(activity_date ,'2020-01-01') as Age,count(user_id) as Total_unique_payers
from REVENUE
SQL fiddle 如何计算累计收入?
【问题讨论】:
Cum_rev
是来自SUM(amount)
的结果?此外,没有GROUP BY
的聚合与 sql_mode=only_full_group_by 不兼容
您可以通过sum(amount) / count(user_id)
进行操作
年龄应该像这样计算DATEDIFF(activity_date,'2020-01-01')
.. 使用逗号代替。
您的完整查询是什么 - 包括GROUP BY
?这是a fiddle。您可以在小提琴中编辑查询和测试运行,然后单击更新。生成新链接后,只需将其复制并粘贴到您的问题中即可。谢谢
@tcadidot0 的上述建议将有很大帮助。您是否尝试根据每场比赛的最大日期每天生成一条线? (这将对应于年龄列)
【参考方案1】:
使用 mysql 5.7 的唯一方法是使用它的变量系统,尽管它有效。它模拟了@Used_By_Already 在他的answer 上使用的窗口函数
既然你提到你关心差距,你首先需要创建一个日期表,这很容易做到:
create table dates_view (
date_day date
);
insert into dates_view
select date_add( '2019-12-31', INTERVAL @rownum:=@rownum+1 day ) as date_day
from (
select 0 union select 1 union select 2 union select 3
union select 4 union select 5 union select 6
union select 7 union select 8 union select 9
) a, (
select 0 union select 1 union select 2 union select 3
union select 4 union select 5 union select 6
union select 7 union select 8 union select 9
) b, (select @rownum:=0) r;
-- Note: each set of select union above will multiply the number
-- of days by 10, so if you need more days in your table just add more
-- set as above "a" or "b" sets
拥有 Dates 表后,您必须将它与当前的 revenue
表交叉连接,但要注意的是,您希望玩家数量独立于累积的 amount
,因此您需要在子查询中独立计算它。
您还需要计算 revenue
表的 max(activity_date)
,以便将结果限制在它之前。
所以下面的查询就可以做到(基于您当前的示例数据):
set @_sum:=0; -- Note: this two lines depends on the client
set @_currGame:=''; -- you are using. Some accumulate variable per session
-- some doesn't, below site, for instance does
select a.game_id,
a.age,
case when @_currGame = game_id
then @_sum:=coalesce(samount,0) + @_sum
else @_sum:=coalesce(samount,0) end as Cum_rev,
a.Total_unique_payers_per_game,
@_currGame := game_id varComputeCurrGame
from
(
select players.game_id,
rev.samount,
datediff(dv.date_day, '2020-01-01') age,
players.noPlayers Total_unique_payers_per_game
from (select @_sum:=0) am,
dates_view dv
cross join (select max(activity_date) maxDate from revenue) md
on dv.date_day <= md.maxDate
cross join (select game_id, count(distinct user_id) noPlayers
from revenue group by game_id) players
left join (select game_id, activity_date, sum(amount) samount
from revenue group by game_id, activity_date) rev
on players.game_id = rev.game_id
and dv.date_day = rev.activity_date
) a,
(select @_sum:=0) s,
(select @_currGame='') x
order by a.game_id desc, a.age;
这将导致:
game_id age Cum_rev Total_unique_payers_per_game varComputeCurrGame
Racing 0 5 2 Racing
Racing 1 5 2 Racing
Racing 2 5 2 Racing
Racing 3 7 2 Racing
DH 0 0 3 DH
DH 1 15 3 DH
DH 2 117 3 DH
DH 3 117 3 DH
在这里看到它的工作(你需要运行它):https://www.db-fiddle.com/f/qifZ6hmpvcSZYwhLDv613d/2
这是支持窗口函数的 MySQL 8.x 版本:
select distinct agetable.game_id,
agetable.age,
sum(coalesce(r1.amount,0))
over (partition by agetable.game_id
order by agetable.game_id, agetable.age) as sm,
agetable.ttplayers
from
(
select r.game_id, dv.date_day, datediff(dv.date_day, '2020-01-01') age, p.ttplayers
from dates_view dv
cross join (select distinct game_id, activity_date from revenue) r
on dv.date_day <= (select max(activity_date) from revenue)
left join (select game_id, count(distinct user_id) ttplayers from revenue group by game_id) p
on r.game_id = p.game_id
group by r.game_id desc, dv.date_day, age, p.ttplayers
) agetable
left join revenue r1
on agetable.date_day = r1.activity_date
and r1.game_id = agetable.game_id
order by agetable.game_id desc, agetable.age
【讨论】:
【参考方案2】:对于以下内容,您需要支持over()
子句(MySQL 8+)的 MySQL 版本 - 我在下面使用了 MariaDB 10.4(我尝试时 MySQL 8 无法在该站点工作)
✓ ✓create table revenue ( game_id varchar(255), user_id varchar(255), amount int, activity_date varchar(255) ); insert into revenue (game_id, user_id, amount, activity_date) values ('Racing', 'ABC123', 5, '2020-01-01'), ('Racing', 'ABC123', 1, '2020-01-04'), ('Racing', 'CDE123', 1, '2020-01-04'), ('DH', 'CDE123', 100, '2020-01-03'), ('DH', 'CDE456', 10, '2020-01-02'), ('DH', 'CDE789', 5, '2020-01-02'), ('DH', 'CDE456', 1, '2020-01-03'), ('DH', 'CDE456', 1, '2020-01-03');
游戏ID |用户 ID |活动日期 |金额 |运行总和 | Total_unique_payers :-------- | :-------- | :------------ | -----: | ----------: | ------------------: 赛车 | ABC123 | 2020-01-01 | 5 | 5 | 4 DH | CDE456 | 2020-01-02 | 10 | 15 | 4 DH | CDE789 | 2020-01-02 | 5 | 20 | 4 DH | CDE123 | 2020-01-03 | 100 | 120 | 4 DH | CDE456 | 2020-01-03 | 1 | 122 | 4 DH | CDE456 | 2020-01-03 | 1 | 122 | 4 赛车 | ABC123 | 2020-01-04 | 1 | 123 | 4 赛车 | CDE123 | 2020-01-04 | 1 | 124 | 4SELECT game_id , user_id , activity_date , amount , sum(amount) over(order by activity_date, user_id) as running_sum , (select count(distinct user_id) from revenue) as Total_unique_payers from revenue order by activity_date , user_id
db小提琴here
在 over 子句中更改计算顺序会影响运行总和的计算方式:例如
游戏ID |用户 ID |活动日期 |金额 |运行总和 | Total_unique_payers :-------- | :-------- | :------------ | -----: | ----------: | ------------------: 赛车 | ABC123 | 2020-01-01 | 5 | 5 | 4 赛车 | ABC123 | 2020-01-04 | 1 | 6 | 4 赛车 | CDE123 | 2020-01-04 | 1 | 7 | 4 DH | CDE456 | 2020-01-02 | 10 | 17 | 4 DH | CDE789 | 2020-01-02 | 5 | 22 | 4 DH | CDE123 | 2020-01-03 | 100 | 122 | 4 DH | CDE456 | 2020-01-03 | 1 | 124 | 4 DH | CDE456 | 2020-01-03 | 1 | 124 | 4SELECT game_id , user_id , activity_date , amount , sum(amount) over(order by game_id DESC, activity_date, user_id) as running_sum , (select count(distinct user_id) from revenue) as Total_unique_payers from revenue order by game_id DESC , activity_date , user_id
db小提琴here
【讨论】:
可以在8之前的版本中完成,没有窗口功能。 我不是说不能……只是我选择这样做 您提到需要支持over
的版本。因此我的评论,它会更准确它可以使用支持over
的版本来完成:)
@Used_By_Already - 这是使用over
子句的好主意。我在预期输出中添加了几行以获得更清晰的结果。但是运行总和正在计算查询中的所有 game_id。累计收入应按如下方式计算,例如,对于 game_id Racing,我们在 2020-01-01 开始时金额为 5,因此 Age 为 0。在 2020-01-02 时,金额仍为 5,因为我们没有交易那天。 2020-01-03 金额为 5。但 2020-01-04 金额为 7,因为我们在这一天有 2 笔交易
@JorgeCampos "For the following..." (即我建议的解决方案)您确实需要支持over()
的版本,但如果您选择遵循其他解决方案,那么您可能不需要。即对于我建议的解决方案,有一个版本要求。我真的觉得我已经足够具体了。以上是关于SQL计算一段时间内的累积值的主要内容,如果未能解决你的问题,请参考以下文章