我正在尝试使用 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;
更简单的甚至可能更快。
请注意当 stime
和 etime
都正好落在 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/