【问题标题】:Find overlapping date ranges in PostgreSQL在 PostgreSQL 中查找重叠的日期范围
【发布时间】:2011-05-27 17:07:34
【问题描述】:

这是正确的吗?

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 有球员姓名、球队名称以及他加入和离开俱乐部的日期。
我想创建一个函数,列出特定年份球队中的所有球员。
上面的查询似乎不起作用...

【问题讨论】:

  • 没有错误..结果不正确..
  • 那么您没有指定条件的数据

标签: sql postgresql overlap date-range


【解决方案1】:

currently accepted answer 没有回答问题。而且原则上是错误的。 a BETWEEN x AND y 翻译为:

<strike>a &gt;= x AND a <b>&lt;=</b> y</strike>

包含上限,而人们通常需要排除它:

a &gt;= x AND a <b>&lt;</b> y

使用日期,您可以轻松调整。对于 2009 年,使用“2009-12-31”作为上限。
但它并不像 timestamps 那样简单,它允许小数位数。现代 Postgres 版本在内部使用一个 8 字节整数来存储最多 6 个小数秒(微秒分辨率)。知道了这一点,我们可以仍然让它工作,但这并不直观,并且取决于实现细节。坏主意。

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

<b>b</b> &gt;= x AND a <b>&lt;</b> 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 对我们不利,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);

SQL OVERLAPS 运算符

The manual:

OVERLAPS 自动取该对中较早的值作为 开始。每个时间段都被认为代表半开 间隔start &lt;= time &lt; 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

支持索引的范围类型

在 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 &amp;&amp; 可以使用 GiST 索引支持查询。

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

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

相关:

【讨论】:

  • 我会投票给你,但你知道没有人会看到它已经 2 岁以上了,对吧? :)
  • @ScottMarlowe:谢谢。好吧,对您的回答进行了编辑,将其添加到“活动”列表中,我无法避免纠正对我来说似乎不正确的内容。所以是对一个人的强迫症采取行动并获得支持的地方,不是吗? :)
  • 同意。是的,到目前为止,重叠是更好的答案。
  • @Erwin Brandstetter:我也遇到了与 'between' 相同的问题。我的数据库功能也无法与“之间”正常工作。你的解释很完美。点赞。
  • 这是最完整、最正确的答案,因为其他答案并不能解决玩家在BEFORE加入团队并在要求的时间后离开的情况。
【解决方案2】:

为什么不使用 between 没有日期部分的东西:

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

或者类似的?

【讨论】:

  • SQL 规范不需要大写。为什么要编辑我的答案?看,大写不一定更容易阅读。约书亚,请不要编辑我的答案,因为它冒犯了你的大写敏感性。
  • 对于那些学习的人来说,我认为它更容易阅读,因为它非常清楚地将 SQL 关键字与开发人员可编辑的对象名称区分开来。
  • 我认为 OP 是在询问如何比较日期范围。这个条件不是要检查一个日期时间是否在一个时间范围内吗?
猜你喜欢
  • 1970-01-01
  • 2014-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-19
  • 1970-01-01
相关资源
最近更新 更多