当前接受的答案不回答问题。而且原则上是错误的。a BETWEEN x AND y
翻译为:
a >= x AND a <= y
包括上限,而人们通常需要排除它:
a >= x AND a < y
有了日期,您可以轻松调整。对于 2009 年,使用“2009-12-31”作为上限。
但它并不像允许小数的时间戳那么简单。现代 Postgres 版本在内部使用一个 8 字节整数来存储最多 6 个小数秒(微秒分辨率)。知道了这一点,我们仍然可以让它工作,但这并不直观,并且取决于实现细节。馊主意。
此外,a BETWEEN x AND y
没有找到重叠的范围。我们需要:
b >= x AND a < y
从未离开过的球员还没有被考虑在内。
正确答案
假设 year 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
.
“一个不知道已经离开的球员被假定为今天为球队效力。”
细化查询:
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);
运算符优先级对我们不利,AND
绑定 before 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);
SQLOVERLAPS
运算符
手册:
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
具有索引支持的范围类型
在 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
范围类型会增加一些开销并占用更多空间。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));
有关的: