PostgreSQL将时间范围分成几天

Cmd*_*ozy 5 sql postgresql date-range generate-series

我正在尝试使用PostgreSQL 9.2.4编写一个复杂的查询,但我无法正常工作.我有一个包含时间范围的表,以及其他几个列.当我在此表中存储数据时,如果所有列都相同且时间范围重叠或相邻,我将它们组合成一行.

但是当我检索它们时,我想在天边界处分割范围 - 例如:

2013-01-01 00:00:00 to 2013-01-02 23:59:59
Run Code Online (Sandbox Code Playgroud)

将被选为两行:

2013-01-01 00:00:00 to 2013-01-01 23:59:59
2013-01-02 00:00:00 to 2013-01-02 23:59:59
Run Code Online (Sandbox Code Playgroud)

对于两个检索到的条目,其他列中的值相同.

我已经看到这个问题似乎或多或少地解决了我想要的问题,但它适用于PostgreSQL的"非常旧"版本,所以我不确定它是否仍然适用.

我也看到了这个问题,它完全符合我的要求,但据我所知,该CONNECT BY语句是SQL标准的Oracle扩展,所以我不能使用它.

我相信我可以使用PostgreSQL来实现这一点generate_series,但我希望有一个简单的例子可以证明它是如何用来做到这一点的.

这是我目前正在处理的查询,目前无效(因为我无法FROM在连接的子查询中引用该表),但我相信这或多或少是正确的轨道.

这里是架构,示例数据和我的工作查询的小提琴.

更新:我刚刚发现一个有趣的事实,感谢这个问题,如果你SELECT在查询的一部分中使用set-returning函数,PostgreSQL将"自动"在集合和行上进行交叉连接.我想我已接近完成这项工作.

Erw*_*ter 9

首先,你的上边界概念被打破了.时间戳23:59:59并不好.数据类型timestamp具有小数位数.怎么样2013-10-18 23:59:59.123::timestamp

包括下边框并在逻辑中的任何位置排除上边框.相比:

在此前提的基础上:

Postgres 9.2或更高版本

SELECT id
     , stime
     , etime
FROM   timesheet_entries t
WHERE  etime <= stime::date + 1  -- this includes upper border 00:00

UNION ALL
SELECT id
     , CASE WHEN stime::date = d THEN stime ELSE d END     -- AS stime
     , CASE WHEN etime::date = d THEN etime ELSE d + 1 END -- AS etime
FROM (
   SELECT id
        , stime
        , etime
        , generate_series(stime::date, etime::date, interval '1d')::date AS d
   FROM   timesheet_entries t
   WHERE  etime > stime::date + 1
   ) sub
ORDER  BY id, stime;
Run Code Online (Sandbox Code Playgroud)

或者干脆:

SELECT id
     , CASE WHEN stime::date = d THEN stime ELSE d END     -- AS stime
     , CASE WHEN etime::date = d THEN etime ELSE d + 1 END -- AS etime
FROM (
   SELECT id
        , stime
        , etime
        , generate_series(stime::date, etime::date, interval '1d')::date AS d
   FROM   timesheet_entries t
   ) sub
ORDER  BY id, stime;
Run Code Online (Sandbox Code Playgroud)

更简单的可能更快.
注意角落情况的差异何时stimeetime两者00:00完全相同.然后在末尾添加具有零时间范围的行.有各种方法可以解决这个问题.我提议:

SELECT *
FROM  (
   SELECT id
        , CASE WHEN stime::date = d THEN stime ELSE d END     AS stime
        , CASE WHEN etime::date = d THEN etime ELSE d + 1 END AS etime
   FROM (
      SELECT id
           , stime
           , etime
           , generate_series(stime::date, etime::date, interval '1d')::date AS d
      FROM   timesheet_entries t
      ) sub1
   ORDER  BY id, stime
   ) sub2
WHERE  etime <> stime;
Run Code Online (Sandbox Code Playgroud)

Postgres 9.3+

在Postgres 9.3+中你最好用LATERAL

SELECT id
     , CASE WHEN stime::date = d THEN stime ELSE d END     AS stime
     , CASE WHEN etime::date = d THEN etime ELSE d + 1 END AS etime
FROM   timesheet_entries t
     , LATERAL (SELECT d::date
                FROM   generate_series(t.stime::date, t.etime::date, interval '1d') d
                ) d
ORDER  BY id, stime;
Run Code Online (Sandbox Code Playgroud)

手册中的详细信息.
与上述相同的角落情况.

SQL Fiddle演示了所有.

  • 这是一个非常棒的答案.它不仅解决了我遇到的问题,还解决了一两个我甚至不知道的问题.谢谢!:) (2认同)
  • @CmdrMoozy:有一个值得注意的**角落**.我为此添加了修复程序.另外,我会对查询与[Pavel函数](http://stackoverflow.com/a/19456611/939860)的比较感兴趣.如果您碰巧测试两者,您可能会留下结果说明. (2认同)