从 SQL 到 SPL:将现有数据对齐到相应位置,缺失补 0
MySQL 数据库有个采样表,每个 ITEM、CITY 是一个采样任务,一个采样任务包括 1 到 5 条记录,这表示 5 周内采集完数据。START_Y 和 START_W 是开始采样的年份和周号,FIRST_USE_Y 和 FIRST_USE_W 是实际采到数据的年份和周号。周号的计算规则:从当前的 1 月 1 日起,每 7 天算一周,依次累计。
ITEM |
CITY |
START_Y |
START_W |
FIRST_USE_Y |
FIRST_USE_W |
VALUE |
A |
NEW YORK |
2023 |
30 |
2023 |
32 |
15000 |
A |
LONDON |
2024 |
2 |
2024 |
2 |
12000 |
A |
LONDON |
2024 |
2 |
2024 |
5 |
50000 |
B |
NEW YORK |
2023 |
49 |
2024 |
1 |
19540 |
B |
MADRID |
2023 |
10 |
2023 |
11 |
15444 |
现在要把每个采样任务扩展成 5 条记录(周),从开始采样的年份和周号开始依次递增,包括实际采集到数据的和没有采集到数据的那些周,前者要对齐到相应的位置, 后者的 VALUE 为 0。
ITEM |
CITY |
START_Y |
START_W |
FIRST_USE_Y |
FIRST_USE_W |
VALUE |
A |
NEW YORK |
2023 |
30 |
2023 |
30 |
0 |
A |
NEW YORK |
2023 |
30 |
2023 |
31 |
0 |
A |
NEW YORK |
2023 |
30 |
2023 |
32 |
15000 |
A |
NEW YORK |
2023 |
30 |
2023 |
33 |
0 |
A |
NEW YORK |
2023 |
30 |
2023 |
34 |
0 |
A |
LONDON |
2024 |
2 |
2024 |
2 |
12000 |
A |
LONDON |
2024 |
2 |
2024 |
3 |
0 |
A |
LONDON |
2024 |
2 |
2024 |
4 |
0 |
A |
LONDON |
2024 |
2 |
2024 |
5 |
50000 |
A |
LONDON |
2024 |
2 |
2024 |
6 |
0 |
B |
NEW YORK |
2023 |
49 |
2023 |
49 |
0 |
B |
NEW YORK |
2023 |
49 |
2023 |
50 |
0 |
B |
NEW YORK |
2023 |
49 |
2023 |
51 |
0 |
B |
NEW YORK |
2023 |
49 |
2023 |
52 |
0 |
B |
NEW YORK |
2023 |
49 |
2024 |
1 |
19540 |
B |
MADRID |
2023 |
10 |
2023 |
10 |
0 |
B |
MADRID |
2023 |
10 |
2023 |
11 |
15444 |
B |
MADRID |
2023 |
10 |
2023 |
12 |
0 |
B |
MADRID |
2023 |
10 |
2023 |
13 |
0 |
B |
MADRID |
2023 |
10 |
2023 |
14 |
0 |
SQL:
WITH ItemCity As (
SELECT Item, City, MIN( DATEADD(day, Start_W*7, DATEFROMPARTS(Start_Y, 1, 1)) ) As StartWeek
FROM Data
GROUP BY Item, City
),
ItemCityWeeks As (
SELECT Item,City, StartWeek
,Year(StartWeek) As Start_Y,datepart(week, StartWeek)-1 As Start_W
,YEAR(DATEADD(day, Weeks.num*7, StartWeek)) As First_Use_Y
,DATEPART(dayofyear, DATEADD(day, Weeks.num*7, StartWeek))/7 As First_Use_W
FROM ItemCity
CROSS JOIN ( VALUES (0), (1), (2), (3), (4)) Weeks(num)
)
SELECT icw.Item, icw.City
, icw.Start_Y, icw.Start_W, icw.First_Use_Y, icw.First_Use_W
, coalesce(d.value, 0) as Value
FROM ItemCityWeeks icw
LEFT JOIN Data d ON d.Item = icw.Item AND d.City = icw.City
and d.First_Use_Y = icw.First_Use_Y and d.First_Use_W = icw.First_Use_W
ORDER BY Item, City DESC
SQL 分组后必须立刻汇总,不能保持分组子集,将每个子集扩展成 N 条记录,用过滤的方法简单对齐 VALUE,只能绕路解决,先分组汇总再扩展,扩展时要用叉乘,对齐 VALUE 时要用多字段 join 的方法间接实现,结构非常复杂,代码也很啰嗦。
SPL 代码要简单易懂很多:
A |
|
1 |
=mySQL.query("select * from data") |
2 |
=A1.group@u(ITEM,CITY) |
3 |
=A2.news(5;ITEM,CITY,START_Y,START_W, (d=elapse(date(START_Y,1,1),7*START_W+(~-1)*7),year(d)):FIRST_USE_Y, int(ceil(interval(pdate@y(d),d)/ 7)):W, ifn(A2.~.select@1(FIRST_USE_W==W).VALUE,0):VALUE) |
4 |
=A3.rename(W:FIRST_USE_W) |
A1:加载数据。
A2:按任务分组,可以保留分组子集不汇总。
A3:将每组数据直接扩展为 5 条记录,按规则计算周号,简单过滤出当前周号对应的 VALUE。函数 pdate@y 返回日期所在年份的第一天,ifn 返回第一个非 null 的成员,select@1 过滤出第一条符合条件的记录。
问题来源:https://stackoverflow.com/questions/78263465/how-to-resample-an-sql-database
英文版 https://c.scudata.com/article/1737104385896