从 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 代码要简单易懂很多: https://try.esproc.com/splx?3T0
A | |
1 | $select * from data.txt |
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.esproc.com/article/1737104385896