sql - PostgreSQL 将时间范围拆分为天

标签 sql postgresql date-range generate-series

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

不过,当我检索它们时,我想在日期边界处拆分范围 - 例如:

2013-01-01 00:00:00 to 2013-01-02 23:59:59

将被选为两行:

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

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

我看过this question这似乎或多或少地解决了我想要的问题,但它适用于“非常旧”的 PostgreSQL 版本,所以我不确定它是否真的仍然适用。

我也看过 this question ,这正是我想要的,但据我所知,CONNECT BY 语句是 Oracle 对 SQL 标准的扩展,所以我不能使用它。

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

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

Here's the fiddle包含架构、示例数据和我的工作查询。

更新:感谢this question,我刚刚发现了一个有趣的事实,如果您在查询的 SELECT 部分使用集合返回函数,PostgreSQL 将“自动”对集合和行进行交叉连接。我想我快要开始工作了。

最佳答案

首先,您的上边界概念已损坏。带有 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;

或者简单地说:

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;

更简单的甚至可能更快。
请注意当 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;

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;

Details in the manual .
与上面相同的极端情况。

SQL Fiddle展示所有。

关于sql - PostgreSQL 将时间范围拆分为天,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19454943/

相关文章:

mysql - 使用最后日期对同一用户进行 SQL 查询

sql - 在一个更新查询中更新 100k 条记录

sql - 使用电子邮件地址作为主键?

vba - vba中的输入框日期范围?

python - 我如何在 sqlalchemy ORM 中表达这个查询?

sql - 使用多列组标识符获取最新的预测数据

sql - 如何比较分组后的所有列?

sql-server - 如何在 SQL Server 中查找重叠的日期

java - 如何将 PutMappingRequest 与 Elasticsearch 8 Java API 客户端结合使用?

sql - 从 SSIS 调用时删除语句失败