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 滚动窗口唯一计数的主要内容,如果未能解决你的问题,请参考以下文章

如何有效地计算熊猫时间序列中的滚动唯一计数?

BigQuery:如何执行滚动时间戳窗口组计数,每天产生行

时间序列中的 SQL 滚动计数

BigQuery:如何在滚动时间戳窗口内对行进行分组和计数?

pandas 基于值而不是计数的窗口滚动计算

pandas 基于值而不是计数的窗口滚动计算