Приетият в момента отговор не отговаря на въпроса. И е грешно по принцип. 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 ограничения, таблицата 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 x date
=8 байта; 1 x daterange
=14 байта на диск или 17 байта в RAM. Но в комбинация с оператора за припокриване &&
заявката може да се поддържа с GiST индекс.
Освен това няма нужда от специални NULL стойности. NULL означава "отворен диапазон" в тип диапазон - точно това, от което се нуждаем. Дефиницията на таблицата дори не трябва да се променя:можем да създадем типа на диапазона в движение - и да поддържаме заявката със съвпадащ индекс на израз:
CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
Свързано:
- Средна таблица на фондовата история