В настоящее время принятый ответ не отвечает на вопрос. И это в принципе неправильно. a BETWEEN x AND y
переводится как:
<strike>a >= x AND a <b><=</b> y</strike>
Включая верхнюю границу, в то время как людям обычно необходимо исключить it:
a >= x AND a <b><</b> y
С даты вы можете легко настроить. Для 2009 года используйте «2009-12-31» в качестве верхней границы.
Но это не так просто с отметками времени , которые допускают дробные цифры. Современные версии 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
.
А нам может нужно предусмотреть особый случай: игроки, которые никогда не уходили. Предполагая, что эти игроки имеют NULL в 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
связывается до 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
оператор
Руководство:
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 х date
= 8 байт; 1 x daterange
= 14 байт на диске или 17 байт в оперативной памяти. Но в сочетании с оператором перекрытия &&
запрос может поддерживаться индексом GiST.
Кроме того, нет необходимости в специальных значениях NULL. NULL означает «открытый диапазон» в типе диапазона - именно то, что нам нужно. Определение таблицы даже не нужно менять: мы можем создать тип диапазона на лету - и поддержать запрос с соответствующим индексом выражения:
CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
Связанный: