如何创建一个有效的查询,该查询将按特定时间间隔计算记录?

Posted

技术标签:

【中文标题】如何创建一个有效的查询,该查询将按特定时间间隔计算记录?【英文标题】:How to create an efficient query which will count of the records by a specific time interval? 【发布时间】:2016-08-05 17:59:33 【问题描述】:

我使用哪个数据库?

我使用的是 PostgreSQL 9.5。

我需要什么?

这是我的data_store 表的一部分:

  id |          starttime
-----+----------------------------
 185 | 2011-09-12 15:24:03.248+02
 189 | 2011-09-12 15:24:03.256+02    
 312 | 2011-09-12 15:24:06.112+02
 313 | 2011-09-12 15:24:06.119+02
 450 | 2011-09-12 15:24:09.196+02
 451 | 2011-09-12 15:24:09.203+02
 452 | 2011-09-12 15:24:09.21+02
 ... |            ...

我想创建一个查询,该查询将按特定时间间隔计算记录。例如,对于 4 秒的时间间隔 - 查询应该返回给我这样的内容:

    starttime-from   |    starttime-to     |  count
---------------------+---------------------+---------
 2011-09-12 15:24:03 | 2011-09-12 15:24:07 |    4
 2011-09-12 15:24:07 | 2011-09-12 15:24:11 |    3
 2011-09-12 15:24:11 | 2011-09-12 15:24:15 |    0
         ...         |         ...         |   ...

最重要的事情:

    时间间隔取决于用户的选择。它可能是1 second37 seconds50 minutes 或一些混合:2 month and 30 mintues。时间间隔的可用单位:millisecondsecondminutehourdaymonthyear。如您所见,我需要针对那个的一些通用/通用查询我还可以为每个单元创建多个查询 - 这不是问题。 查询应该是高效的,因为我在一个大型数据库中工作(2000 万行或更多,但在查询中我只使用该数据库的一部分,例如:100 万行)。

问题是:查询应该如何实现?

我尝试转换我在以下线程中找到的解决方案,但我没有成功:

PostgreSQL: running count of rows for a query 'by minute', Group by data intervals, Best way to count records by arbitrary time intervals in Rails+Postgres。

我有什么?

我删除了帖子的这一部分,以提高帖子的透明度。本节没有必要回答我的问题。如果您想了解这里的内容,请查看帖子的历史记录。

【问题讨论】:

【参考方案1】:

您的查询似乎很复杂。您只需要生成时间序列,然后使用left join 将它们组合在一起。 . .和聚合:

select g.ts,  g.ts + interval '4 second', count(ds.id)
from (select generate_series(min(starttime), max(strttime), interval '4 second') as ts
      from data_store
     ) g left join
     data_store ds
     on ds.starttime >= g.ts and ds.starttime < g.ts + interval '4 second'
group by g.ts
order by g.ts;

注意:如果您希望间隔从精确的秒开始(并且在 1000 次中没有 999 次中有一些奇怪的毫秒数),请使用 date_trunc()

编辑:

如果相关子查询更快,可能值得一看:

select gs.ts,
       (select count(*)
        from data_store ds
        where ds.starttime >= g.ts and ds.starttime < g.ts + interval '4 second'
       ) as cnt
from (select generate_series(min(starttime), max(strttime), interval '4 second') as ts
      from data_store
     ) g;

【讨论】:

我确信我的查询太复杂了。您的查询证明了这一点。感谢您的帮助,它完全符合我的要求。我当然会接受这个答案,但在此之前你能告诉你是否可以让你的查询更有效率吗?如果我使用像60 minutes 这样的大间隔,它会运行得更快,但是对于像4 seconds 这样的小间隔 - 在等待 30 分钟后,我放弃了。我知道这取决于很多事情。首先,我在 starttime 列上创建了 B-Tree 索引,但它没有帮助。你有什么想法,我怎样才能提高你的查询速度? 罗伯特。 . .如果上述编辑不起作用,请询问另一个有关性能的问题——包括得到正确答案但性能不佳的查询。可能还有其他方法,但我认为它们混淆了这个问题的答案。 第二个查询比第一个慢,所以我会保留第一个查询。 顺便说一句:根据你的第一个查询和我的查询,我终于找到了更快的查询。我刚刚发布了这个查询的答案,所以如果有人想尝试一下,请查看here【参考方案2】:

如果有帮助,我会使用 UDF 创建动态日期/时间范围。

在 SomeDate 的连接中使用结果>=DateR1 和 SomeDate

Range、DatePart 和 Increment 是参数

Declare @Date1 DateTime = '2011-09-12 15:24:03 '
Declare @Date2 DateTime = '2011-09-12 15:30:00 '
Declare @DatePart varchar(25)='SS'
Declare @Incr int=3


Select DateR1 = RetVal
    ,DateR2 = LEAD(RetVal,1,@Date2) OVER (ORDER BY RetVal)
From (Select * from [dbo].[udf-Create-Range-Date](@Date1,@Date2,@DatePart,@Incr) ) A
Where RetVal<@Date2

返回

DateR1                  DateR2
2011-09-12 15:24:03.000 2011-09-12 15:24:06.000
2011-09-12 15:24:06.000 2011-09-12 15:24:09.000
2011-09-12 15:24:09.000 2011-09-12 15:24:12.000
2011-09-12 15:24:12.000 2011-09-12 15:24:15.000
2011-09-12 15:24:15.000 2011-09-12 15:24:18.000
2011-09-12 15:24:18.000 2011-09-12 15:24:21.000
...
2011-09-12 15:29:48.000 2011-09-12 15:29:51.000
2011-09-12 15:29:51.000 2011-09-12 15:29:54.000
2011-09-12 15:29:54.000 2011-09-12 15:29:57.000
2011-09-12 15:29:57.000 2011-09-12 15:30:00.000

UDF

CREATE FUNCTION [dbo].[udf-Create-Range-Date] (@DateFrom datetime,@DateTo datetime,@DatePart varchar(10),@Incr int)

Returns 
@ReturnVal Table (RetVal datetime)

As
Begin
    With DateTable As (
        Select DateFrom = @DateFrom
        Union All
        Select Case @DatePart
               When 'YY' then DateAdd(YY, @Incr, df.dateFrom)
               When 'QQ' then DateAdd(QQ, @Incr, df.dateFrom)
               When 'MM' then DateAdd(MM, @Incr, df.dateFrom)
               When 'WK' then DateAdd(WK, @Incr, df.dateFrom)
               When 'DD' then DateAdd(DD, @Incr, df.dateFrom)
               When 'HH' then DateAdd(HH, @Incr, df.dateFrom)
               When 'MI' then DateAdd(MI, @Incr, df.dateFrom)
               When 'SS' then DateAdd(SS, @Incr, df.dateFrom)
               End
        From DateTable DF
        Where DF.DateFrom < @DateTo
    )

    Insert into @ReturnVal(RetVal) Select DateFrom From DateTable option (maxrecursion 32767)

    Return
End

-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','YY',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','DD',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-31','MI',15) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-02','SS',1) 

【讨论】:

感谢您的帮助。也许我会试试看。【参考方案3】:

改进了所选答案中的查询。

我刚刚改进了您可以在所选答案中找到的查询。

最终查询如下:

SELECT gp.tp AS starttime_from, gp.tp + interval '4 second' AS starttime_to, count(ds.id)
FROM (SELECT generate_series(min(starttime),max(starttime), interval '4 second') as tp
      FROM data_store
      WHERE id_user_table=1 and sip='147.32.84.138'
      ORDER BY 1
     ) gp 
     LEFT JOIN data_store ds 
     ON ds.id_user_table=1 and ds.sip='147.32.84.138' 
        and ds.starttime >= gp.tp and ds.starttime < gp.tp + interval '4 second'
GROUP BY starttime_from

我已将ORDER BY 移至子查询。现在它有点快。我还在WHERE 子句中添加了所需的列。最后,我在查询中经常使用的列上创建了多列索引:

CREATE INDEX my_index ON data_store (id_user_table, sip, starttime);

目前查询速度非常快。 注意:对于非常小的时间间隔,查询的结果包括很多零计数行。这些行占用了空间。在这种情况下,查询应该包含 HAVING count(ds.id) &gt; 0 限制,但是您必须在客户端处理这些 0。

另一种解决方案

这个解决方案没有前面的那么快,但是下面的查询没有使用多列索引,它仍然很快。

您可以在此答案的末尾找到查询中的两个重要内容:

'second' 是截断输入值的精度。您还可以选择其他精度,例如:millisecondminuteday 等。

'4 second' 是时间间隔。时间间隔可以有其他单位,如millisecondminuteday等。

这里你可以找到查询的解释:

generate_period 查询生成从指定日期时间到指定日期时间的间隔。您可以手动或通过表格的列(如我的情况)指示此特定日期时间。对于4秒的时间间隔,查询返回:

          tp
---------------------
 2011-09-12 15:24:03
 2011-09-12 15:24:07
 2011-09-12 15:24:11
         ...

data_series 查询计算日期时间特定精度的记录:for 1 second time intervalfor 1 day time interval 等。在我的情况下,特定精度是 'second',所以 for 1 second time interval 是选择操作的结果不包括不发生的日期时间的 0 值。在我的例子中,data_series 查询返回:

       starttime     |    ct
---------------------+-----------
 2011-09-12 15:24:03 |     2
 2011-09-12 15:24:06 |     2
 2011-09-12 15:24:09 |     3     
         ...         |    ...

最后,查询的最后一部分总结了特定时间段的ct 列。查询返回:

    starttime-from   |    starttime-to     |   ct
---------------------+---------------------+---------
 2011-09-12 15:24:03 | 2011-09-12 15:24:07 |    4
 2011-09-12 15:24:07 | 2011-09-12 15:24:11 |    3
 2011-09-12 15:24:11 | 2011-09-12 15:24:15 |    0
         ...         |         ...         |   ...

这是查询:

WITH generate_period AS(

    SELECT generate_series(date_trunc('second',min(starttime)), 
                           date_trunc('second',max(starttime)), 
                           interval '4 second') as tp
    FROM data_store 
    WHERE id_user_table=1 --other restrictions

), data_series AS(

    SELECT date_trunc('second', starttime) AS starttime, count(*) AS ct
    FROM data_store  
    WHERE id_user_table=1 --other restrictions
    GROUP  BY 1

)

SELECT gp.tp AS starttime-from, 
       gp.tp + interval '4 second' AS starttime-to, 
       COALESCE(sum(ds.ct),0) AS ct
FROM  generate_period gp
LEFT JOIN data_series ds ON date_trunc('second',ds.starttime) >= gp.tp 
                        and date_trunc('second',ds.starttime) < gp.tp + interval '4 second'
GROUP BY 1
ORDER BY 1;

【讨论】:

以上是关于如何创建一个有效的查询,该查询将按特定时间间隔计算记录?的主要内容,如果未能解决你的问题,请参考以下文章

查询时在Django TimeStampedModel中指定时间间隔[重复]

hsqldb 中的间隔格式异常无效,但查询在 Postgres 中有效

如何使此查询也返回计数值为 0 的行?

要计算的特定 SQL 查询 [重复]

mysql查询随机和desc

HIVE/Impala 查询:计算满足特定条件的行之间的行数