18

它是否正确?

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

4

2 回答 2

91

当前接受的答案不回答问题。而且原则上是错误的。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,除非startend相等,在这种情况下它代表单个时间瞬间。

照顾潜在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));

有关的:

于 2013-03-08T23:48:56.010 回答
6

为什么不在没有日期部分的情况下使用:

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

或类似的东西?

于 2010-12-19T05:33:56.867 回答