Найти перекрывающиеся диапазоны дат в 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));

по теме: