SQL 滚动窗口唯一计数
Posted
技术标签:
【中文标题】SQL 滚动窗口唯一计数【英文标题】:SQL rolling window unique count 【发布时间】:2018-05-04 00:30:38 【问题描述】:pandas 中是否有与这行代码等效的 SQL?
假设 a 是一个 DataFrame 对象,索引是一个时间列表(包括小时、分钟和秒)。
在这种情况下,x 将只是 DataFrame 中除索引之外的另一列。
a.rolling('1h').apply(lambda x: len(np.unique(x))).astype(int)
示例结果:(时间格式为 HH:MM:SS)
X
05:20:19 4 <- 1 (only 1 unique number)
05:20:19 5 <- 2 (4 and 5 are unique) * same time as before
05:37:18 7 <- 3 (4, 5 and 7 are unique)
05:45:14 4 <- 3 (4, 5, and 7)
05:56:04 4 <- 3 (4, 5, and 7)
06:18:48 6 <- 4 (now 4, 5, 6, and 7)
06:48:34 3 <- 3 (only checks past hour, so now 3, 4, 6)
07:52:48 1 <- 1 (only time in past hour, so only 1)
我也只是为此使用普通 SQL。
非常感谢!
【问题讨论】:
用您正在使用的数据库标记您的问题。 刚刚做到了!感谢您的提醒 【参考方案1】:以下示例适用于 BigQuery 标准 SQL
#standardSQL
WITH `project.dataset.your_table` AS (
SELECT TIME '05:20:19' t, 4 x UNION ALL
SELECT TIME '05:37:18', 7 UNION ALL
SELECT TIME '05:45:14', 4 UNION ALL
SELECT TIME '05:56:04', 4 UNION ALL
SELECT TIME '06:18:48', 5 UNION ALL
SELECT TIME '06:48:34', 3 UNION ALL
SELECT TIME '07:52:48', 1
)
SELECT
t, x, (SELECT COUNT(DISTINCT y) FROM UNNEST(arr) y) uniques
FROM (
SELECT t, x,
ARRAY_AGG(x)
OVER(ORDER BY TIME_DIFF(t, TIME '00:00:00', SECOND)
RANGE BETWEEN 3600 PRECEDING AND CURRENT ROW) arr
FROM `project.dataset.your_table`
)
-- ORDER BY t
结果为
Row t x uniques
1 05:20:19 4 1
2 05:37:18 7 2
3 05:45:14 4 2
4 05:56:04 4 2
5 06:18:48 5 3
6 06:48:34 3 3
7 07:52:48 1 1
它使用您问题中的精确虚拟数据 - 我觉得实际上您没有 TIME 而是 TIMESTAMP 所以您可能想要使用 ORDER BY TIME_DIFF(t, TIME '00:00:00', SECOND)
而不是 ORDER BY TIMESTAMP_DIFF(t, TIMESTAMP '2000-01-01 00:00:00', SECOND)
这样您的查询将如下所示
#standardSQL
WITH `project.dataset.your_table` AS (
SELECT TIMESTAMP '2018-01-05 05:20:19' t, 4 x UNION ALL
SELECT TIMESTAMP '2018-01-05 05:37:18', 7 UNION ALL
SELECT TIMESTAMP '2018-01-05 05:45:14', 4 UNION ALL
SELECT TIMESTAMP '2018-01-05 05:56:04', 4 UNION ALL
SELECT TIMESTAMP '2018-01-05 06:18:48', 5 UNION ALL
SELECT TIMESTAMP '2018-01-05 06:48:34', 3 UNION ALL
SELECT TIMESTAMP '2018-01-05 07:52:48', 1
)
SELECT
t, x, (SELECT COUNT(DISTINCT y) FROM UNNEST(arr) y) uniques
FROM (
SELECT t, x,
ARRAY_AGG(x)
OVER(ORDER BY TIMESTAMP_DIFF(t, TIMESTAMP '2000-01-01 00:00:00', SECOND)
RANGE BETWEEN 3600 PRECEDING AND CURRENT ROW) arr
FROM `project.dataset.your_table`
)
-- ORDER BY t
结果为
Row t x uniques
1 2018-01-05 05:20:19.000 UTC 4 1
2 2018-01-05 05:37:18.000 UTC 7 2
3 2018-01-05 05:45:14.000 UTC 4 2
4 2018-01-05 05:56:04.000 UTC 4 2
5 2018-01-05 06:18:48.000 UTC 5 3
6 2018-01-05 06:48:34.000 UTC 3 3
7 2018-01-05 07:52:48.000 UTC 1 1
更新 - 以下是解决您的额外新要求的“技巧”
#standardSQL
WITH `project.dataset.your_table` AS (
SELECT TIME '05:20:19' t, 4 x UNION ALL
SELECT TIME '05:20:19', 5 UNION ALL
SELECT TIME '05:37:18', 7 UNION ALL
SELECT TIME '05:45:14', 4 UNION ALL
SELECT TIME '05:56:04', 4 UNION ALL
SELECT TIME '06:18:48', 6 UNION ALL
SELECT TIME '06:48:34', 3 UNION ALL
SELECT TIME '07:52:48', 1
)
SELECT
t, x, (SELECT COUNT(DISTINCT y) FROM UNNEST(arr) y) uniques
FROM (
SELECT t, x,
ARRAY_AGG(x)
OVER(ORDER BY TIME_DIFF(t, TIME '00:00:00', MILLISECOND) + 1000 * RAND()
RANGE BETWEEN 3600000 PRECEDING AND CURRENT ROW) arr
FROM `project.dataset.your_table`
)
-- ORDER BY t
结果为
Row t x uniques
1 05:20:19 5 1
2 05:20:19 4 2
3 05:37:18 7 3
4 05:45:14 4 3
5 05:56:04 4 3
6 06:18:48 6 4
7 06:48:34 3 3
8 07:52:48 1 1
还有一个更新:o)
#standardSQL
WITH `project.dataset.your_table` AS (
SELECT TIME '05:20:19' t, 4 x UNION ALL
SELECT TIME '05:20:19', 5 UNION ALL
SELECT TIME '05:37:18', 7 UNION ALL
SELECT TIME '05:45:14', 4 UNION ALL
SELECT TIME '05:56:04', 4 UNION ALL
SELECT TIME '06:18:48', 6 UNION ALL
SELECT TIME '06:48:34', 3 UNION ALL
SELECT TIME '07:52:48', 1
)
SELECT
t, x, (SELECT COUNT(DISTINCT y) FROM UNNEST(arr) y) uniques
FROM (
SELECT t, x,
ARRAY_AGG(x)
OVER(ORDER BY ms
RANGE BETWEEN 3600000 PRECEDING AND CURRENT ROW) arr
FROM (
SELECT t, x, TIME_DIFF(t, TIME '00:00:00', MILLISECOND) + 1000 * RAND() ms
FROM `project.dataset.your_table`
)
)
-- ORDER BY t
【讨论】:
哇!非常感谢您的详细解答!我对其进行了测试并注意到,对于相同值的时间,它会显示整体唯一计数,而不是逐行增加的数量。我已经用我的想法更新了我的示例结果。我真的很抱歉没有早点把那个案子包括在内。这是一个简单的解决方法吗? 不。这是不可修复的!除非您将引入一些逻辑来确定示例中的第一行应计为第一行,第二行应计为第二行。现在它们处于完全相同的时间点“05:20:19”,因此从rolling window
逻辑的角度来看,它们都对彼此不同的计数有所贡献。例如,如果您的时间戳中有毫秒,并且这两行的时间不同 - 这将是一个“技巧”
... 同时,总是有解决方法 - 请参阅答案中的更新/添加。希望它是不言自明的(我只是添加假随机毫秒)-希望这对您有用:o)。如果您有其他字段,您可以将其用作对具有相同确切时间的行进行正确排序的提示 - 您可以使用它来代替“假随机”毫秒值
谢谢米哈伊尔!我测试了新的,但我收到错误消息,说由于 OVER(ORDER BY) 调用,我已经超出了分配的资源。我已经在写入另一个表并允许大数据,所以看起来 OVER(ORDER BY) 正在使用过多的内存。我的数据集很大(2+ GB)所以这可能就是原因
这是有道理的。回答你最初的问题 - 我不太担心优化并将计算毫秒的逻辑放在 OVER(ORDER BY) 中。查看另一项更新,希望能解决内存问题。同时,当用户在没有任何分区的情况下使用 ORDER BY 时,这是一个已知问题。因此,如果您仍然有问题 - 您应该考虑以某种方式对您的数据进行分区。同时,至少考虑投票给答案 - 如果它以某种方式帮助你!【参考方案2】:
对于 mysql,您可以使用子查询,见下文:
SELECT t1.date
, (SELECT count(DISTINCT t2.x) FROM mytable AS t2
WHERE t2.date <= t1.date
AND t2.date > DATE_SUB(t1.date, INTERVAL 1 HOUR)
) AS uniq_rolling_count_of_x
FROM mytable AS t1
ORDER BY 1
;
【讨论】:
谢谢!除了 google bigquery 不允许嵌套的选定语句(抱歉之前没有澄清)并且它不会增加我的示例结果中的计数行之外,该查询大部分都可以工作。当它应该逐行递增时,此查询的结果将在我的示例结果中返回前 5 行的“3”。我想知道这是否是一个简单的修复,但我似乎想不出一个 您需要在 BigQuery 中使用标准 SQL 才能在 SELECT 列表中使用子选择。 感谢您的建议!我尝试了标准 sql,但我得到了这个错误:LEFT OUTER JOIN cannot be used without a condition that is a equal of fields from the join【参考方案3】:以时间范围关系作为连接条件,将表与自身连接起来。这是 MySQL 的语法:
SELECT t1.time, t1.x, COUNT(DISTINCT t2.x)
FROM yourTable AS t1
JOIN yourTable AS t2 ON t2.time BETWEEN DATE_SUB(t1.time, INTERVAL 1 HOUR) AND t1.time
GROUP BY t1.time, t1.x
DEMO
【讨论】:
谢谢!除了在我的示例结果中它不增加计数行之外,该查询大部分都有效。当它应该逐行递增时,此查询的结果将在我的示例结果中为前 5 行返回“3”。我想知道这是否是一个简单的修复,但我似乎想不出一个 我把 ON 子句中的表倒过来了。现在可以使用了。 谢谢!我看了一下演示,它似乎确实有效。我想提到的一种情况(我很抱歉没有早点包括在内)是存在相同价值的时间。如果它们具有相同的值,则您提供的代码不会增加具有相同时间值的行的计数,而是提供整体唯一计数。我也用这个案例更新了我的样本结果。这是一个简单的解决方法吗? 时间相同,为什么要得到不同的计数?它如何决定哪一个得到较低的计数?是否有对它们进行排序的 ID 列?还是应该由 X 订购?以上是关于SQL 滚动窗口唯一计数的主要内容,如果未能解决你的问题,请参考以下文章