在PostgreSQL中查找重叠的date范围
它是否正确?
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
有球员姓名,球队名称和他join和离开俱乐部的date。
我想列出一个函数,列出特定年份所有在队中的球员。
上述查询似乎并没有工作…
为什么不使用date部分之间的东西:
WHERE datefield BETWEEN '2009-10-10 00:00:00' AND '2009-10-11 00:00:00'
或类似的东西?
接受的答案是不好的
build议a BETWEEN x AND y
的答案得到了很多upvotes,并被接受了近2年。 但这不但不能回答问题,原则上也是错误的。
a BETWEEN x AND y
转换就是:
a >= x AND a <= y
包括上边界,而人们通常需要排除它:
a >= x AND a < y
用date,你可以很容易地调整。 2009年使用“2009-12-31”作为上边界。
但是对于允许小数位的时间戳来说并不那么简单。 现代Postgres版本在内部使用一个8字节的整数来存储多达6个小数秒(μs分辨率)。 知道这一点,我们仍然可以使其工作,但这不是直观的,取决于实施细节。 馊主意。
此外,在这种特殊情况下, a BETWEEN x AND y
之间无法find与另一个范围重叠的范围。 我们需要的是:
b >= x AND a < y
而从来没有离开的球员还没有被考虑。
正确的答案
假设在2009
,我会重写这个问题而不改变它的含义:
“find2010年之前join的所有球员,并且在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';
运算符优先级对我们有效, AND
在OR
之前进行绑定。 我们需要括号。
如果参照完整性没有被破坏,表格team
本身就是这个查询中的噪声,可以被删除。
虽然同一个玩家可以离开并重新join同一个团队,但我们也需要折叠可能的重复,例如DISTINCT
。
我们可能需要提供一个特殊情况:从未离开过的玩家。 假设这些玩家有date_leave IS 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);
相关的答案与优化DISTINCT
(如果重复是常见的):
- 多桌多桌 – performance糟糕
通常, 名称不是唯一的,代理主键用于自然人。 但显然, name_player
是player
的主要关键。 如果您只需要玩家名称,则不需要查询中的表格player
:
SELECT DISTINCT c.name_player FROM contract c 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);
我们也可以使用SQL OVERLAPS
操作符 :
OVERLAPS
自动以该对的较早值作为开始。 每个时间段被认为代表半开区间start <= time < end
,除非start
和end
相等,在这种情况下它代表单个时刻。
但是我们需要注意可能的NULL
值。 使用COALESCE
最简单:
SELECT DISTINCT c.name_player FROM contract c WHERE t.name_team = ? AND (c.date_join, COALESCE(c.date_leave, current_date)) OVERLAPS ('2009-01-01'::date, '2009-12-31'::date)
在Postgres 9.2或更高版本中,您还可以将实际范围types与重叠操作符&&
(可以使用GiST索引支持)结合使用。 例:
- 平均库存历史表