在PostgreSQL中查找重叠的日期范围

时间:2010-12-18 23:22:39

标签: php sql postgresql overlap date-range

这是对的吗?

SELECT * 
FROM   contract 
JOIN   team USING (name_team) 
JOIN   player USING(name_player) 
WHERE  name_team = ? 
AND    DATE_PART('YEAR',date_join)>= ? 
AND    DATE_PART('YEAR',date_leave)<= ?

我的桌子contract有球员姓名,球队名称以及他加入和离开俱乐部的日期 我想制作一个功能,列出特定年份球队中的所有球员 以上查询似乎不起作用......

2 个答案:

答案 0 :(得分:59)

currently accepted answer没有回答这个问题。原则上这是错误的。 a BETWEEN x AND y转换为:

a >= x AND a <= y

包括上边框,而人们通常需要排除

a >= x AND a < y

使用日期,您可以轻松调整。 2009年使用'2009-12-31'作为上边界 但是 timestamps 允许小数位并不是那么简单。现代Postgres版本在内部使用8字节整数来存储最多6个小数秒(μs分辨率)。知道这一点,我们可以仍然可以使它工作,但这不是直观的,取决于实现细节。不好的主意。

此外,a BETWEEN x AND y找不到重叠范围。我们需要:

b >= x AND a < y

从未离开的玩家尚未被考虑。

正确答案

假设年份 2009 ,我会在不改变其含义的情况下重新解释这个问题:

“查找在2010年之前加入并且在2009年之前没有离开的特定团队的所有玩家。”

基本查询:

SELECT p.* 
FROM   team     t
JOIN   contract c USING (name_team) 
JOIN   player   p USING (name_player) 
WHERE  t.name_team = ? 
AND    c.date_join  <  date '2010-01-01'
AND    c.date_leave >= date '2009-01-01';

但还有更多:

如果使用FK约束强制执行参照完整性,则表team本身只是查询中的噪声,可以删除。

虽然同一个玩家可以离开并重新加入同一个团队,但我们还需要折叠可能的重复项,例如使用DISTINCT

我们可能需要提供特殊情况:从未离开的玩家。假设这些玩家在date_leave中有NULL。

“一名不知道已经离开的球员被假定为今天为球队效力。”

精炼查询:

SELECT DISTINCT p.* 
FROM   contract c
JOIN   player   p USING (name_player) 
WHERE  c.name_team = ? 
AND    c.date_join  <  date '2010-01-01'
AND   (c.date_leave >= date '2009-01-01' OR c.date_leave IS NULL);

Operator precedence对我们起作用,ANDOR之前绑定。我们需要括号。

优化DISTINCT的相关答案(如果重复是常见的):

通常,自然人的名称不是唯一的,并且使用代理主键。但是,显然name_playerplayer的主键。如果你需要的只是玩家名字,我们在查询中不需要表player

SELECT DISTINCT name_player 
FROM   contract
WHERE  name_team = ? 
AND    date_join  <  date '2010-01-01'
AND   (date_leave >= date '2009-01-01' OR date_leave IS NULL);

SQL OVERLAPS运算符

The manual:

  

OVERLAPS自动将该对的较早值作为   开始。每个时间段被认为代表半开放   间隔start <= time < end,除非startend相等   它表示单一时刻。

为了处理潜在的NULL值,COALESCE似乎最简单:

SELECT DISTINCT name_player 
FROM   contract
WHERE  name_team = ? 
AND    (date_join, COALESCE(date_leave, CURRENT_DATE)) OVERLAPS
       (date '2009-01-01', date '2010-01-01');  -- upper bound excluded

具有索引支持的范围类型

在Postgres 9.2或更高版本中,您还可以使用实际的range types进行操作:

SELECT DISTINCT name_player 
FROM   contract
WHERE  name_team = ? 
AND    daterange(date_join, date_leave) &&
       daterange '[2009-01-01,2010-01-01)';  -- upper bound excluded

范围类型会增加一些开销并占用更多空间。 2 x date = 8个字节;磁盘上1 x daterange = 14个字节或RAM中17个字节。但结合overlap operator &&,可以使用GiST索引支持查询。

此外,不需要特殊情况的NULL值。 NULL表示范围类型中的“开放范围” - 正是我们需要的。表定义甚至不必更改:我们可以动态创建范围类型 - 并使用匹配的表达式索引支持查询:

CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));

相关:

答案 1 :(得分:6)

为什么不在没有日期部分之间使用:

WHERE datefield BETWEEN '2009-10-10 00:00:00' AND '2009-10-11 00:00:00'

或类似的东西?