Reputation: 431
I need to write a query to get the sum of values for each category for a list of given dates. If a value doesn't exist for a category, we should get the value from the previous date. Basically something like "max per category per date". The end goal is a trend chart. If a previous value for a category doesn't exist, setting the value to 0
is fine.
See tables and result below:
Category
id | name |
---|---|
1 | savings |
2 | cash |
3 | stocks |
Item
id | categoryId | value | createdAt |
---|---|---|---|
1 | 1 | 100 | 2022-01-01 |
2 | 2 | 20 | 2022-01-01 |
3 | 3 | 500 | 2022-01-01 |
4 | 2 | 0 | 2022-01-02 |
5 | 3 | 1000 | 2022-01-03 |
Result
createdAt | total |
---|---|
2022-01-01 | 620 |
2022-02-02 | 600 |
2022-02-03 | 1100 |
To get a result for a single date I could do something like this:
SELECT SUM(value) as total
FROM Category
LEFT JOIN (
SELECT id, categoryId, value
FROM Item
WHERE id IN (
SELECT MAX(id) FROM Item WHERE createdAt <= '2022-01-10' GROUP BY categoryId)
) items ON Category.id = items.categoryId;
I have absolutely no clue on how to approach doing this for multiple dates, eg. if my input would be every day in the month of January 2022. I'm running on MySQL 8.0.23. Also, if this is not feasible with a single query, I'm up for ideas. Do you have any suggestions?
Upvotes: 1
Views: 1419
Reputation: 65363
One option uses window functions such as SUM() OVER ()
and LAG()
such as
WITH i AS
(
SELECT SUM(`value`) OVER (PARTITION BY `createdAt`,`categoryId` ORDER BY `createdAt`) AS total_sofar,
LAG(`value`,1,0) OVER (PARTITION BY `categoryId` ORDER BY `createdAt`) AS lg,
`createdAt`
FROM Item
)
SELECT DISTINCT `createdAt`,
SUM(total_sofar) OVER (ORDER BY `createdAt`)-SUM(lg) OVER (ORDER BY `createdAt`) AS total
FROM i
ORDER BY `createdAt`
as you have MySQL DBMS of version 8.0. The trick is grouping(partitioning by categoryId
along with the LAG
at the first query)
Upvotes: 0
Reputation: 4796
Try this:
with u as
(select id as categoryId from Category),
v as
(select distinct createdAt from Item),
w as
(select * from u cross join v),
x as
(select createdAt,
categoryId,
(select value
from Item
where categoryId = w.categoryId and createdAt <= w.createdAt
order by createdAt desc
limit 1) as value
from w)
select createdAt, sum(value) as total
from x
group by createdAt
Basically getting all the combinations of the creation dates with the categoryIds, then using a subquery to get the value of the closest or equal date for each categoryId.
A Fiddle.
Upvotes: 1