这是对的吗?
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
有球员姓名,球队名称以及他加入和离开俱乐部的日期
我想制作一个功能,列出特定年份球队中的所有球员
以上查询似乎不起作用......
答案 0 :(得分:59)
currently accepted answer没有回答这个问题。原则上这是错误的。 a BETWEEN x AND y
转换为:
a >= x AND a <= y
包括上边框,而人们通常需要排除:
a >= x AND a < y
使用日期,您可以轻松调整。 2009年使用'2009-12-31'作为上边界 但是 timestamps 允许小数位并不是那么简单。现代Postgres版本在内部使用8字节整数来存储最多6个小数秒(μs分辨率)。知道这一点,我们可以仍然可以使它工作,但这不是直观的,取决于实现细节。不好的主意。
此外,a BETWEEN x AND y
找不到重叠范围。我们需要:
b >= x AND a < 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);
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
在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));
相关:
答案 1 :(得分:6)
为什么不在没有日期部分之间使用:
WHERE datefield BETWEEN '2009-10-10 00:00:00' AND '2009-10-11 00:00:00'
或类似的东西?