Найти перекрывающиеся диапазоны дат в PostgreSQL
это правильно?
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
имя игрока, название команды и дату когда он пришел и покинул клуб.
Я хочу сделать функцию, перечисляющую всех игроков, которые были в команде в определенные годы.
Приведенный выше запрос не работает ...
2 ответов
почему бы не использовать между Без даты дело:
WHERE datefield BETWEEN '2009-10-10 00:00:00' AND '2009-10-11 00:00:00'
или что-то подобное?
на в настоящее время принимаются ответ не отвечает на вопрос. И это в принципе неправильно. a BETWEEN x AND y
переводится как:
a >= x AND a <= y
в том числе верхняя граница, в то время как людям обычно нужно исключить это:
a >= x AND a < y
С времени вы можете легко отрегулировать. Для 2009 года используйте' 2009-12-31 ' в качестве верхней границы.
Но это не так просто, с метки, которые позволяют дробные цифры. Современные версии 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, таблица это просто шум в запросе и может быть удален.
в то время как один и тот же игрок может уйти и присоединиться к той же команде, нам также нужно сложить возможные дубликаты, например, с 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 x date
= 8 байт; 1 x daterange
= 14 байт на диске или 17 байт в ОЗУ. Но в сочетании с оператор перекрытия &&
запрос может поддерживаться индексом GiST.
кроме того, нет необходимости в специальных нулевых значениях. NULL означает "открытый диапазон" в типе диапазона-именно то, что нам нужно. Определение таблицы даже не нужно изменять: мы можем создать тип диапазона на лету - и поддерживать запрос с соответствующим выражением индекс:
CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
по теме: