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

aoc*_*ira 12 sql postgresql date-range overlap

它是否正确?

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)<= ?
Run Code Online (Sandbox Code Playgroud)

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

Erw*_*ter 68

目前接受的答案不回答这个问题.原则上这是错误的.a BETWEEN x AND y翻译为:

a >= x AND a <= y
Run Code Online (Sandbox Code Playgroud)

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

a >= x AND a < y
Run Code Online (Sandbox Code Playgroud)

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

而且,a BETWEEN x AND y没有找到重叠的范围.我们需要:

b >= x AND a < y
Run Code Online (Sandbox Code Playgroud)

从未离开的球员尚未考虑.

适当的答案

假设这一年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';
Run Code Online (Sandbox Code Playgroud)

但还有更多:

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

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

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

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

精炼查询:

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);
Run Code Online (Sandbox Code Playgroud)

运算符优先级对我们起作用,AND之前绑定OR.我们需要括号.

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

通常,自然人的姓名不是唯一的,并且使用代理主键.但是,显然,name_player是主要的关键player.如果您只需要玩家名称,我们不需要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);
Run Code Online (Sandbox Code Playgroud)

SQL OVERLAPS运算符

手册:

OVERLAPS自动将该对的较早值作为开始.每个时间段被认为代表半开时间间隔start <= time < end,除非start并且end相等,在这种情况下它代表单个时间瞬间.

要照顾潜在的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
Run Code Online (Sandbox Code Playgroud)

具有索引支持的范围类型

在Postgres 9.2或更高版本中,您还可以使用实际范围类型进行操作:

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
Run Code Online (Sandbox Code Playgroud)

范围类型增加了一些开销并占用更多空间.2 x date= 8字节; 1 x daterange=磁盘上的14个字节或RAM中的17个字节.但是与重叠运算符&&结合使用时,可以使用GiST索引支持查询.

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

CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
Run Code Online (Sandbox Code Playgroud)

有关:

  • 同意.到目前为止,重叠是更好的答案. (5认同)
  • @ScottMarlowe:谢谢.好吧,对你的答案的编辑将这一个列入"活动"列表,我无法避免纠正对我来说似乎不正确的事情.SO是在一个人的OCD上行动起来并获得投票的地方,不是吗?:) (3认同)
  • @ScottMarlowe,我刚才看到了。 (2认同)

Sco*_*owe 6

为什么不在没有日期部分的情况下使用:

WHERE datefield BETWEEN '2009-10-10 00:00:00' AND '2009-10-11 00:00:00'
Run Code Online (Sandbox Code Playgroud)

或类似的东西?

  • 对于那些学习,我认为它更容易阅读,因为它非常清楚地区分SQL关键字和开发人员可编辑对象名称. (23认同)
  • SQL SPEC为什么不需要编辑我的答案?看,大写不一定更容易阅读.约书亚,请不要编辑我的答案,因为它会冒犯你的大写情绪. (7认同)