【问题标题】:Count overlapping times per minute stored as a column计算存储为列的每分钟重叠次数
【发布时间】:2017-09-17 06:44:17
【问题描述】:

我在红移数据库中有一组记录,每个记录都有一个指示活动开始的时间戳和一个指示活动结束的时间戳。

timestamp_start     |timestamp_end
2017-01-01 01:01:31 |2017-01-01 01:48:31
2017-01-01 01:02:35 |2017-01-01 02:08:35
2017-01-01 01:09:10 |2017-01-01 02:18:10
2017-01-01 01:10:05 |2017-01-01 02:00:05
2017-01-01 01:14:58 |2017-01-01 01:56:58
2017-01-01 01:19:10 |2017-01-01 02:18:10
2017-01-01 01:25:10 |2017-01-01 01:54:10
2017-01-01 01:30:23 |2017-01-01 01:56:23
2017-01-01 01:36:26 |2017-01-01 03:06:26
2017-01-01 01:37:03 |2017-01-01 02:14:03
2017-01-01 01:37:15 |2017-01-01 02:08:15
2017-01-01 01:37:55 |2017-01-01 02:58:55
2017-01-01 01:42:49 |2017-01-01 02:59:49
2017-01-01 01:44:10 |2017-01-01 03:23:10
2017-01-01 01:46:49 |2017-01-01 02:58:49
2017-01-01 01:49:34 |2017-01-01 02:15:34
2017-01-01 01:52:11 |2017-01-01 02:38:11
2017-01-01 01:52:45 |2017-01-01 03:31:45
2017-01-01 01:54:15 |2017-01-01 02:17:15
2017-01-01 01:55:14 |2017-01-01 02:40:14

这是一种计算每分钟新活动发生次数的简单方法,方法如下:

select date_trunc('minute', timestamp_start) as minute, count(*) as count
  from myTable
  group by 1

类似地计算一个活动的结束:

select date_trunc('minute', timestamp_end) as minute, count(*) as count
  from myTable
  group by 1

但是,我如何计算每分钟“正在进行”的活动[编辑:在某个范围内的每一分钟]?即在分钟组 01:01 我们有一个新的活动开始。在 01:02 分钟,我们又开始了一个新的活动,但是从 01:01 开始的活动还没有结束,因此当前活动的计数是两个。相反,02:00 的记录计数必须包括在该分钟之前完成的 4 条记录中的任何一条。

此外,解决方案也不能“爆炸”数据,即将记录连接到“准备”分钟的不同表以提供记录的多个重复项,然后计算结果表的长度。

我尝试了以下方法:

SELECT
  minute,
  count(CASE WHEN timestamp_end > minute AND timestamp_start < minute) AS tmp
        FROM (
          SELECT minute
          FROM (
                 (
                   SELECT date_trunc('minute', timestamp_start) AS minute
                   FROM myTable
                   GROUP BY 1
                 )
                 UNION ALL (
                   SELECT date_trunc('minute', timestamp_end) AS minute
                   FROM myTable
                   GROUP BY 1
                 )
               ) s1
          GROUP BY 1)

但是,我怀疑我的案例论证形成错误,很可能会误用它。我也查看了窗口函数,但是,我看不到一种明显的方法来向前计数以仅包含“当前活动”记录。

【问题讨论】:

  • 很遗憾,您当前的查询既有逻辑问题,也有句法问题;所以你可能不得不回到绘图板上。第一个问题:您想如何确定输出中包含哪些分钟?在某个范围内的每一分钟?每分钟都有一个非零计数?其他...?
  • 能否根据您提供的示例输出将预期结果添加到您的问题中?你的描述很透彻,但恐怕还是有误会的余地
  • @MarkAdelsberger 在某个范围内的每一分钟,都会更新
  • @DaveRGP 好的,但请注意,您在某些时候肯定需要做一些“爆炸”。即使您将结果限制在特定范围内,您也希望结果集包含原始表中不存在的数据点。为了实现这一点,您要么需要“分解”数据,要么进行一些程序式计算(如 TSQL 中的循环)。纯 SQL 无法做到这一点。
  • @DaveRGP - 公平地说,没有像样的数据库会通过复制事件记录的N 来实现 InfiniteRefactor 描述的查询。正如我在回答中所说,如果对此的担忧是推测性的(而不是基于观察到的这种方法的问题),那么我真的认为应该在被拒绝之前对其进行测试。

标签: sql count amazon-redshift


【解决方案1】:

更新 - 最后的另一个建议,基于 Stefano Zanini 的想法...


最初的想法

要在一分钟内生成输出,您当然可以执行类似的操作

select count(*)
  from myTable
 where $minute between timestamp_start and timestamp_end

从 SQL 的角度来看,说您希望在该事件范围内为每分钟的输出行计算每条记录,但又不想根据分钟列表“爆炸”数据,这有点的一个矛盾。我假设您关心的是运行查询的性能和/或资源使用情况;我认为这对于足够大的数据集可能是一个问题,但如果您还没有测试这种方法,它仍然值得测试。

另一个可行的选择

现在,正如我评论 Stefano Zanini 的回答一样,他的建议并不完全奏效。但它确实提出了一种可行的方法:首先捕获计数发生变化的分钟数,然后进行后处理以获取每分钟的值。

获取计数可能更改的分钟列表

select distinct minute from (
    select date_trunc('minute', timestamp_start) as minute from my_table
    union all
    select dateadd('m', 1, date_trunc('minute', timestamp_start)) as minute from my_table
)

这可能会让您加入对桌的时间要少得多。

您也许可以通过使用外部联接(并采取措施避免重复记录;尚未解决)来获得更像他的查询的东西,但是自联接会导致相同级别的“数据爆炸”作为这个版本的原始方法。

对于他的查询可能有什么问题存在一些争议,所以考虑一下:

这两种方法都尝试限制“爆炸”数据的采样时间,然后尝试计算与每个选定采样分钟对应的数据。

Stefano 选择样本的方式的功能问题是他只选择事件的开始时间。但实际上计数也可以在事件结束时改变。所以考虑

Start         Stop
10:00         10:15
10:05         10:20

现在,Stefano 的查询将生成 10:00 和 10:05 的记录,您必须插入任何其他分钟的值。您将正确地推断出 1 从 10:00 到 10:04 的值,以及从 10:05 到 10:15 的值 2。但是您会推断出 10:16 的值是 2,因为查询并没有告诉您当时有什么变化。这是错误的。

现在您可以增加查询的复杂性来解决这个问题;但计数逻辑在他的版本中已经更加间接/复杂(并且仅仅因为他列出了不同计数方法的权衡并不意味着其中任何一种都是精确的或适合特定目的)。或者你可以用一种简单直接的方式来做。


如果这些都不起作用(或者您出于其他原因不想使用它),那么我不知道 SQL 是您完成这项工作所需的工具。也许是一个迭代几分钟并将计数累积到临时表中的过程?或者如果数据库和用户之间有一个服务层(java或其他东西),那么也许在那里进行计数?

【讨论】:

  • 谢谢,这也是我得出的结论,数据很大,目前的方法是“爆炸”。我的任务是“避免爆炸”,虽然 tbf我没有受到工具的限制。我是 sql 新手,想确保我没有忽略一些东西。
  • 您介意提供一个示例,这样我的解决方案就会失败吗?
  • @StefanoZanini :在您的第一个评论之后会很乐意讨论,但在您的第二个评论之后?你自己一个人。
  • @MarkAdelsberger 删除了我的评论,因为这让你很生气,请继续
  • @MarkAdelsberger,我认为您需要使用 as 来分配别名以使其工作。我肯定在 Redshift 中这样做。
【解决方案2】:

我认为该任务可以通过自连接来完成,左表的开始日期介于右侧表行的开始日期和结束日期之间。

select  t1.timestamp_start, count(*)
from    test t1
join    test t2
on      t1.timestamp_start >= t2.timestamp_start and
        t1.timestamp_start < t2.timestamp_end
group by t1.timestamp_start

这很好用,但输出不是每分钟的计数,而是每timestamp_start 的计数。从该字段中提取分钟提出了一个新问题:第 37 分钟和第 52 分钟开始的任务不止一项,您应该为它们显示多少计数?

这是最大/最小选项

select  date_trunc('minute', tt.timestamp_start),
        max(tt.cnt) as max_cnt, /* this is probably the value you want */
        min(tt.cnt) as min_cnt
from    (
            select  t1.timestamp_start, count(*) as cnt
            from    test t1
            join    test t2
            on      t1.timestamp_start >= t2.timestamp_start and
                    t1.timestamp_start < t2.timestamp_end
            group by t1.timestamp_start
        ) tt
group by date_trunc('minute', tt.timestamp_start)

sum 选项更简单(注意下面的查询给出的结果与上面用sum 聚合的查询相同,因为分组条件不那么严格):

select  date_trunc('minute', t1.timestamp_start), count(*)
from    test t1
join    test t2
on      t1.timestamp_start >= t2.timestamp_start and
        t1.timestamp_start < t2.timestamp_end
group by date_trunc('minute', t1.timestamp_start)

您可以找到一个工作示例here; rextester 没有红移模拟器,但 SQL Server 有执行相同任务的函数,所以没什么大不了的。

【讨论】:

  • 一般不会产生预期的结果。不会为第一个事件的开始或任何其他在没有其他事件正在进行时开始的事件生成记录。在多个事件开始的几分钟内会产生多个可能相互矛盾的记录。
  • 第一个事件问题在第一个join 条件下使用&gt;= 而不是&gt; 处理,您指出的第二个问题在我的答案的第二部分进行了辩论。您可以在我添加的链接中查看所有正在运行的查询
  • 谢谢,我会测试,但与此同时,“总和选项更简单”是什么意思?我在您的代码中没有看到总和。
  • 你说得对,它可能有点晦涩难懂。我用一些解释编辑了我的答案
猜你喜欢
  • 1970-01-01
  • 2017-03-14
  • 1970-01-01
  • 2019-05-02
  • 2013-02-28
  • 2019-02-21
  • 2021-06-19
  • 2020-03-29
  • 1970-01-01
相关资源
最近更新 更多