PostgreSQL匹配时间戳的开始和结束时间间隔

时间:2011-05-14 08:00:37

标签: postgresql

我正在设计一些系统来存储包含开始和结束时间的记录。例如:

CREATE TABLE test (
  id bigserial PRIMARY KEY,
  ts_start timestamp NOT NULL,
  ts_end timestamp NOT NULL,
  foo bar NOT NULL,
  ...
);

现在我想对此运行查询以查找与某个时间戳重叠的所有行。这将产生一个where子句,如:

WHERE ts_start <= '2006-4-6 12:34:56' AND ts_end > '2006-4-6 12:34:56'

我用大量生成的测试数据对此进行了测试,性能非常糟糕。我使用ts_start上的索引和ts_end上的另一个索引以及ts_start和ts_end上的多列索引来测试它。最后一个给出了最好的结果,但它仍远未达到最佳状态。

问题是postgresql不知道ts_end保证比ts_start大,所以它使用的计划能够找到ts_end小于ts_start的行。

有任何建议如何解决这个问题?

编辑: 对于有这个问题的人,如果你可以再等一会儿,那么PostgreSQL 9.2就有了完美的解决方案:range types。 9.2现在处于测试阶段,最终版本很可能会在2012年底发布。

3 个答案:

答案 0 :(得分:8)

有“时间postgres”(谷歌)但我不知道它是否仍然保持...我相信有关于将这种类型的搜索包含在postgres中的讨论,但我不记得最终状态它的。无论如何:

使用框和要点的示例:

CREATE TABLE segments( start INTEGER NOT NULL, stop INTEGER NOT NULL, range_box BOX NOT NULL );
INSERT INTO segments SELECT n,n+1,BOX(POINT(n,-1),POINT(n+1,1)) FROM generate_series( 1, 1000000 ) n;
CREATE INDEX segments_box ON segments USING gist( range_box );
CREATE INDEX segments_start ON segments(start);
CREATE INDEX segments_stop ON segments(stop);

EXPLAIN ANALYZE SELECT * FROM segments WHERE 300000 BETWEEN start AND stop;
 Index Scan using segments_start on segments  (cost=0.00..12959.24 rows=209597 width=72) (actual time=91.990..91.990 rows=2 loops=1)
   Index Cond: (300000 >= start)
   Filter: (300000 <= stop)
 Total runtime: 92.023 ms

EXPLAIN ANALYZE SELECT * FROM segments WHERE range_box && '(300000,0,300000,0)'::BOX;
 Bitmap Heap Scan on segments  (cost=283.49..9740.27 rows=5000 width=72) (actual time=0.036..0.037 rows=2 loops=1)
   Recheck Cond: (range_box && '(300000,0),(300000,0)'::box)
   ->  Bitmap Index Scan on segments_box  (cost=0.00..282.24 rows=5000 width=0) (actual time=0.032..0.032 rows=2 loops=1)
         Index Cond: (range_box && '(300000,0),(300000,0)'::box)
 Total runtime: 0.064 ms

你可以看到gist指数在这里非常快(1500次!lol) (你可以使用许多运算符,如重叠,包含,包含等等。

http://www.postgresql.org/docs/8.2/static/functions-geometry.html

答案 1 :(得分:2)

您遇到与尝试索引线段然后查询某个点是否在细分中的人相同的问题。你不能通过单独索引每个维度来做到这一点,你需要通过构建某种BSP结构来索引。

我不确定PG是否有任何内置数据类型来支持日期范围,但我确定如果你使用PostGIS将时间范围表示为2D空间中的点,然后告诉PG地理索引那个,您将从此查询中获得最佳性能。

也许有一个日期特定的相当于我的建议内置到pg,但是,正如我所说,我不熟悉它。我熟悉pg的几何索引功能,我认为你应该认真考虑它作为优化。

这是一个简单的例子(虽然我确信它的查询速度非常快):

  1. 将每个时间范围表示为从原点(0,0)到点(从,到)的矩形。
  2. 启用地理位置索引。
  3. 给定时间段P,您可以通过检查点(P,P)是否在具有类似ST_Contains的函数的矩形内来查询它是否在时间内。此查询将为O(log(范围数))。
  4. 说明:

                   |
                   |
                   |
                   |
            to     |
      (timestamp)  |
                   |
                   |
                   |_________________  (from,to)
                   |__               |
                   |  |(p,p)         |
                   |__|______________|_______________________
    
                                    from (timestamp)
    

答案 2 :(得分:0)

  

问题是postgresql不知道ts_end保证比ts_start大,所以它使用的计划能够找到ts_end小于ts_start的行。

在这种情况下,您需要重新表达您的查询,以便将其告诉Postgres。

这就像你在嵌套集中查询lft / rgt时所做的那样:如果你有一个使用lft / rgt索引的树,孩子有parent_lft < lftlft < rgtparent_lft < parent_rgt,最佳查询将依赖于parent_lft < lftlft < parent_rgt(在lft上查找小范围内的索引),而不是parent_lft < lft和{ {1}}(从一点开始在rgt < parent_rgt上查找索引)。

当您添加索引时,您处于类似情况。除非你限制ts_start和ts_end中的任何一个或两个,否则你将会看到一大堆行。

  

现在我想对此运行查询以查找与某个时间戳重叠的所有行。这将产生一个where子句,如:

     

lft

对于该特定查询,您可能希望查看几何类型并使用GIST索引。

具体来说,如果将ts_start和ceil ts_end置于午夜,则可以获得整数表示(例如,自纪元以来的天数)。然后将后者存储为可索引类型,并使用重叠条件进行查询。

作为旁注,有一些关于在最近几个月在pg-hackers列表中添加某种时间戳段/事件类型的讨论,但我很难通过谷歌搜索找不到相关的引用。所以...在这里提一下,如果你比我幸运的话。