currently accepted answer 没有回答问题。而且原则上是错误的。 a BETWEEN x AND y 翻译为:
<strike>a >= x AND a <b><=</b> y</strike>
包含上限,而人们通常需要排除它:
a >= x AND a <b><</b> y
使用日期,您可以轻松调整。对于 2009 年,使用“2009-12-31”作为上限。
但它并不像 timestamps 那样简单,它允许小数位数。现代 Postgres 版本在内部使用一个 8 字节整数来存储最多 6 个小数秒(微秒分辨率)。知道了这一点,我们可以仍然让它工作,但这并不直观,并且取决于实现细节。坏主意。
此外,a BETWEEN x AND y 找不到重叠范围。我们需要:
<b>b</b> >= x AND a <b><</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 <= 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 或更高版本中也可以使用实际的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));
相关: