缺少数据的窗口函数
Posted
技术标签:
【中文标题】缺少数据的窗口函数【英文标题】:Window functions with missing data 【发布时间】:2016-06-08 19:05:21 【问题描述】:假设我有一个表(MyTable
)如下:
item_id | date
----------------
1 | 2016-06-08
1 | 2016-06-07
1 | 2016-06-05
1 | 2016-06-04
1 | 2016-05-31
...
2 | 2016-06-08
2 | 2016-06-06
2 | 2016-06-04
2 | 2016-05-31
...
3 | 2016-05-31
...
我想建立一个每周汇总表,报告一个运行 7 天的窗口。该窗口基本上会显示“在过去 7 天内报告了多少唯一的 item_id
s”?
因此,在这种情况下,输出表如下所示:
date | weekly_ids
----------------------
2016-05-31| 3 # All 3 were present on the 31st
2016-06-01| 3 # All 3 were present on the 31st which is < 7 days before the 1st
2016-06-02| 3 # Same
2016-06-03| 3 # Same
2016-06-04| 3 # Same
2016-06-05| 3 # Same
2016-06-06| 3 # Same
2016-06-07| 3 # Same
2016-06-08| 2 # item 3 was not present for the entire last week so it does not add to the count.
我试过了:
SELECT
item_id,
date,
MAX(present) OVER (
PARTITION BY item_id
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS is_present
FROM (
# Inner query
SELECT
item_id,
date,
1 AS present,
FROM MyTable
)
GROUP BY date
ORDER BY date DESC
这感觉好像它正朝着正确的方向前进。但事实上,当日期不存在(日期太多)时,窗口在错误的时间范围内运行,并且它也不会输出 item_id
不存在时的日期记录(即使它存在在前一天)。这个问题有简单的解决方法吗?
如果有用且有必要
我可以硬编码一个最旧的日期 我还可以获得所有存在的item_id
s 的表格。
此查询将仅在 BigQuery 上运行,因此 BQ 特定函数/语法是公平的游戏,不幸的是,不在 BigQuery 上运行的 SQL 函数/语法对我没有帮助...
【问题讨论】:
输出应该是什么样的? @vkp -- 非常好的问题。我会在一分钟内更新。 @vkp -- 我在这里添加了一些演示输出。也许我应该重新编写问题以使用 2 而不是 7 的窗口大小......我可能会用更少的演示输入更容易编写演示输出......你怎么看?是否足够清晰? 为什么标题为“”而不是“累积计数(不同)”?我觉得我可能误会了什么。 @GordonLinoff -- 你可能不是。我没有写太多 SQL,所以我对普通术语等不是特别熟悉......我这样命名它是因为我正在使用一个由于缺少数据而无法正常运行的窗口函数 (MAX(...) OVER (...)
) .如果你有更好的标题,请随意——或者直接告诉我。我不认为这是一个相当累积的计数,因为它意味着每行数据的一个小“窗口”,但同样,我什么都不知道,这就是我在这里问的原因:- )。
【参考方案1】:
我创建了一个临时表来保存日期,但是,您可能会受益于为这些连接添加一个永久表到您的数据库中。相信我,它会减少头痛。
DECLARE @my_table TABLE
(
item_id int,
date DATETIME
)
INSERT @my_table SELECT 1,'2016-06-08'
INSERT @my_table SELECT 1,'2016-06-07'
INSERT @my_table SELECT 1,'2016-06-05'
INSERT @my_table SELECT 1,'2016-06-04'
INSERT @my_table SELECT 1,'2016-05-31'
INSERT @my_table SELECT 2,'2016-06-08'
INSERT @my_table SELECT 2,'2016-06-06'
INSERT @my_table SELECT 2,'2016-06-04'
INSERT @my_table SELECT 2,'2016-05-31'
INSERT @my_table SELECT 3,'2016-05-31'
DECLARE @TrailingDays INT=7
DECLARE @LowDate DATETIME='01/01/2016'
DECLARE @HighDate DATETIME='12/31/2016'
DECLARE @Calendar TABLE(CalendarDate DATETIME)
DECLARE @LoopDate DATETIME=@LowDate
WHILE(@LoopDate<=@HighDate) BEGIN
INSERT @Calendar SELECT @LoopDate
SET @LoopDate=DATEADD(DAY,1,@LoopDate)
END
SELECT
date=HighDate,
weekly_ids=COUNT(DISTINCT item_id)
FROM
(
SELECT
HighDate=C.CalendarDate,
LowDate=LAG(C.CalendarDate, @TrailingDays,0) OVER (ORDER BY C.CalendarDate)
FROM
@Calendar C
WHERE
CalendarDate BETWEEN @LowDate AND @HighDate
)AS X
LEFT OUTER JOIN @my_table MT ON MT.date BETWEEN LowDate AND HighDate
GROUP BY
LowDate,
HighDate
【讨论】:
对不起...我不确定我是否关注这里。Calendar
来自哪里?
我在这里有点作弊,我在我的日历表中放入了一个包含 +-100 年时间跨度内每一天的条目。这为我节省了数小时和数小时的时间。您只需编写一次脚本,并在需要基于期间的报告时在合理范围内使用它。
更新了 @Temp 表以将日历日期保持在范围内。
太棒了...这看起来很有希望。我可能需要做一些不同的事情才能让它在 BigQuery 上运行,但总体思路似乎很简单......【参考方案2】:
试试下面的例子。它可以为您提供探索的方向 纯 GBQ - Legacy SQL
SELECT date, items FROM (
SELECT
date, COUNT(DISTINCT item_id) OVER(ORDER BY sec RANGE BETWEEN 60*60*24*2 PRECEDING AND CURRENT ROW) AS items
FROM (
SELECT
item_id, date, timestamp_to_sec(timestamp(date)) AS sec
FROM (
SELECT calendar.day AS date, MyTable.item_id AS item_id
FROM (
SELECT DATE(DATE_ADD(TIMESTAMP('2016-05-28'), pos - 1, "DAY")) AS day
FROM (
SELECT ROW_NUMBER() OVER() AS pos, *
FROM (FLATTEN((
SELECT SPLIT(RPAD('', 1 + DATEDIFF(TIMESTAMP(CURRENT_DATE()), TIMESTAMP('2016-05-28')), '.'),'') AS h
FROM (SELECT NULL)),h
)))
) AS calendar
LEFT JOIN (
SELECT date, item_id
FROM
(SELECT 1 AS item_id, '2016-06-08' AS date),
(SELECT 1 AS item_id, '2016-06-07' AS date),
(SELECT 1 AS item_id, '2016-06-05' AS date),
(SELECT 1 AS item_id, '2016-06-04' AS date),
(SELECT 1 AS item_id, '2016-05-28' AS date),
(SELECT 2 AS item_id, '2016-06-08' AS date),
(SELECT 2 AS item_id, '2016-06-06' AS date),
(SELECT 2 AS item_id, '2016-06-04' AS date),
(SELECT 2 AS item_id, '2016-05-31' AS date),
(SELECT 3 AS item_id, '2016-05-31' AS date),
(SELECT 3 AS item_id, '2016-06-05' AS date)
) AS MyTable
ON calendar.day = MyTable.date
)
)
)
GROUP BY date, items
ORDER BY date
请注意
最早的日期 - 2016-05-28 - 在日历子查询中硬编码 窗口大小控制在 60*60*24*2 前行和当前行之间的范围内;如果你需要 7 天 - 表达式应该是 60*60*24*6 记住 BigQuery 旧版 SQL 中 COUNT(DISTINCT) 的细节【讨论】:
以上是关于缺少数据的窗口函数的主要内容,如果未能解决你的问题,请参考以下文章