我希望找到给定时间范围内存在多少个分组间隙。
starting range: 2012-01-12 00:00:00
ending range: 2012-01-18 59:59:59
大致翻译为:
type 10 11 12 13 14 15 16 17 18 19 20
a |--========]
a |==------]
b |==============--]
c |-----===========]
d |--=====================------]
按类型分组的相同数据:
a |--========] |==------]
b |==============--]
c |-----===========]
d |--=====================------]
导致:
type gap
---------
a 1 (yes)
b 1 (yes)
c 1 (yes)
d 0 (no)
最终......
SUM(gap) AS gaps
----------------
3
更新澄清:
数据以每种类型的开始和结束时间戳存储。例如:
id type start_datetime end_datetime
--------------------------------------------------
1 a 2012-01-11 00:00:00 2012-01-14 59:59:59
2 a 2012-01-18 00:00:00 2012-01-20 59:59:59
3 b 2012-01-14 00:00:00 2012-01-19 59:59:59
4 c 2012-01-10 00:00:00 2012-01-15 59:59:59
5 d 2012-01-11 00:00:00 2012-01-20 59:59:59
答案 0 :(得分:2)
为了避免双重工作,这里是数据(我将包含的上边界湾替换为独占的,这是更常见的,恕我直言):
-- CREATE SCHEMA tmp;
DROP TABLE tmp.gaps CASCADE;
CREATE TABLE tmp.gaps
( id INTEGER NOT NULL PRIMARY KEY -- surrogate key
, ztype CHAR(1) NOT NULL
, start_datetime TIMESTAMP NOT NULL -- lower boundary := inclusive
, end_datetime TIMESTAMP NOT NULL -- upper boundary := exclusive
);
CREATE UNIQUE INDEX gaps_forward ON tmp.gaps(ztype,start_datetime);
CREATE UNIQUE INDEX gaps_backward ON tmp.gaps(ztype,end_datetime);
INSERT INTO tmp.gaps(id,ztype,start_datetime,end_datetime) VALUES
(1,'a', '2012-01-11 00:00:00', '2012-01-15 00:00:00' )
,(2,'a', '2012-01-18 00:00:00', '2012-01-21 00:00:00' )
,(3,'b', '2012-01-14 00:00:00', '2012-01-20 00:00:00' )
,(4,'c', '2012-01-10 00:00:00', '2012-01-16 00:00:00' )
,(5,'d', '2012-01-11 00:00:00', '2012-01-21 00:00:00' )
,(6,'e', '2012-01-11 00:00:00', '2012-01-15 00:00:00' ) -- added this
,(7,'e', '2012-01-15 00:00:00', '2012-01-21 00:00:00' ) -- and this
;
-- SELECT * FROM tmp.gaps;
更新:这是CTE。 在第一个UNION中,我在想要的(1月12日 - 19日 - 1月)间隔的左侧和右侧添加了两个假的间隔。
每个ztype我计算间隔的总数。如果没有孔,这应该是一个,如果有一个孔,则应该是两个,等等。这也将为ztype找到在所需间隔中没有任何记录的空白。
-- EXPLAIN ANALYZE
WITH RECURSIVE meuk(ztype,start_datetime,end_datetime) AS (
-- For every possible "ztype" add two dummie records
-- just before and just after our wanted interval.
WITH plus2 AS (
SELECT g0.ztype,g0.start_datetime,g0.end_datetime FROM tmp.gaps g0
WHERE (g0.start_datetime <= '2012-01-12 00:00:00' AND g0.end_datetime >= '2012-01-12 00:00:00')
OR (g0.start_datetime >= '2012-01-12 00:00:00' AND g0.end_datetime <= '2012-01-19 00:00:00')
OR (g0.start_datetime <= '2012-01-19 00:00:00' AND g0.end_datetime >= '2012-01-19 00:00:00')
UNION ALL SELECT DISTINCT g1.ztype, '1900-01-01 00:00:00'::timestamp, '2012-01-12 00:00:00'::timestamp FROM tmp.gaps g1
UNION ALL SELECT DISTINCT g2.ztype, '2012-01-19 00:00:00'::timestamp, '2100-01-01 00:00:00'::timestamp FROM tmp.gaps g2
)
SELECT p0.ztype,p0.start_datetime,p0.end_datetime
FROM plus2 p0
-- the start of a stretch: there is no older overlapping
-- (or touching) interval
WHERE NOT EXISTS (SELECT *
FROM plus2 nx
WHERE nx.ztype = p0.ztype
AND nx.start_datetime < p0.start_datetime -- older
AND nx.end_datetime >= p0.start_datetime -- touching or overlapping
)
UNION
SELECT mk.ztype
, LEAST(mk.start_datetime,p1.start_datetime)
, GREATEST(mk.end_datetime,p1.end_datetime)
FROM plus2 p1
, meuk mk
WHERE p1.ztype = mk.ztype
AND (p1.start_datetime >= mk.start_datetime AND p1.start_datetime <= mk.end_datetime AND p1.end_datetime > mk.end_datetime)
)
SELECT ztype, COUNT(*)-1 AS ngap
FROM meuk mk
WHERE NOT EXISTS (SELECT *
FROM meuk nx
WHERE nx.ztype = mk.ztype
AND (nx.start_datetime,nx.end_datetime) OVERLAPS( mk.start_datetime,mk.end_datetime)
AND (nx.end_datetime - nx.start_datetime) > (mk.end_datetime - mk.start_datetime)
)
GROUP BY ztype
ORDER BY ztype
;
创建最终总和留给读者的练习; - )
结果:
ztype | ngap
-------+------
a | 1
b | 1
c | 1
d | 0
e | 0
(5 rows)
答案 1 :(得分:1)
这是wildplasser使用Windows而不是CTE的答案的变体。基于相同的测试夹具:
select ztype, count(*) as gaps
from (
select ztype, datetime, sum(n) over(partition by ztype order by datetime asc) as level
from (
select id, ztype, start_datetime as datetime, 1 as n from tmp.gaps
union all
select id, ztype, end_datetime, -1 from tmp.gaps
union all
select 0, ztype, '2012-01-12 00:00:00', 0 from (select distinct ztype from tmp.gaps) z
union all
select 0, ztype, '2012-01-19 00:00:00', 0 from (select distinct ztype from tmp.gaps) z
) x
) x
where level = 0 and datetime >= '2012-01-12 00:00:00' and datetime < '2012-01-19 00:00:00'
group by ztype
;
这是基于使用sum()作为窗口聚合,为范围开始加1,为范围结束减1,然后在目标范围内查找运行总和为0的点。我必须做与wildplasser相同的事情,添加一些额外的条目,这些条目在边界的端点没有贡献任何东西,以便找到没有任何东西覆盖边界的组...
这似乎在测试数据上花费较少,但我认为它可能高度依赖于表中没有太多数据要经过。通过一些重新排列(这会使它更难以阅读),它只能对tmp.gaps进行两次完整扫描(其中一次只是获得不同的ztypes)。