Aktualnie przyjęta odpowiedź nie odpowiada na pytanie. I w zasadzie jest to złe. a BETWEEN x AND y
przekłada się na:
a >= x AND a <= y
W tym górna granica, podczas gdy ludzie zazwyczaj muszą wykluczać to:
a >= x AND a < y
Z datami możesz łatwo dostosować. W przypadku roku 2009 jako górną granicę użyj „2009-12-31”.
Ale z sygnaturami czasowymi to nie jest takie proste. które pozwalają na cyfry ułamkowe. Nowoczesne wersje Postgresa używają wewnętrznie 8-bajtowej liczby całkowitej do przechowywania do 6 ułamków sekund (rozdzielczość µs). Wiedząc o tym, moglibyśmy nadal sprawiają, że działa, ale nie jest to intuicyjne i zależy od szczegółów implementacji. Zły pomysł.
Ponadto a BETWEEN x AND y
nie znajduje nakładających się zakresów. Potrzebujemy:
b >= x AND a < y
I gracze, którzy nigdy nie odeszli nie są jeszcze brane pod uwagę.
Właściwa odpowiedź
Zakładając rok 2009
, przeformułuję pytanie, nie zmieniając jego znaczenia:
„Znajdź wszystkich graczy danej drużyny, którzy dołączyli przed 2010 r. i nie opuścili przed 2009 r.”
Zapytanie podstawowe:
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';
Ale jest więcej:
Jeśli integralność referencyjna jest wymuszona ograniczeniami FK, tabela team
samo w sobie jest tylko szumem w zapytaniu i można je usunąć.
Chociaż ten sam gracz może opuścić i ponownie dołączyć do tej samej drużyny, musimy również złożyć ewentualne duplikaty, na przykład za pomocą DISTINCT
.
A my możemy trzeba uwzględnić szczególny przypadek:graczy, którzy nigdy nie odeszli. Zakładając, że ci gracze mają NULL w date_leave
.
„Zakłada się, że zawodnik, o którym nie wiadomo, że odszedł, gra w drużynie do dziś”.
Dopracowane zapytanie:
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);
Pierwszeństwo operatorów działa na naszą niekorzyść, AND
wiąże przed OR
. Potrzebujemy nawiasów.
Powiązana odpowiedź ze zoptymalizowanym DISTINCT
(jeśli duplikaty są częste):
- Stoły od wielu do wielu — wydajność jest zła
Zazwyczaj nazwy osób fizycznych nie są unikatowe i używany jest zastępczy klucz podstawowy. Ale oczywiście name_player
jest głównym kluczem player
. Jeśli potrzebujesz tylko nazw graczy, nie potrzebujemy stołu player
w zapytaniu:
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
operator
Instrukcja:
OVERLAPS
automatycznie przyjmuje wcześniejszą wartość pary jako początek. Każdy okres jest uważany za reprezentujący półotwarty interwał start <= time < end
, chyba że start
i end
są równe, w którym to przypadku reprezentuje ten pojedynczy moment.
Aby zadbać o potencjalne NULL
wartości, COALESCE
wydaje się najłatwiejsze:
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
Typ zakresu z obsługą indeksów
W Postgresie 9.2 lub nowszym możesz także operować z rzeczywistymi typami zasięgu :
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
Typy zakresów dodają trochę narzutu i zajmują więcej miejsca. 2 x date
=8 bajtów; 1 x daterange
=14 bajtów na dysku lub 17 bajtów w pamięci RAM. Ale w połączeniu z operatorem nakładania się &&
zapytanie może być obsługiwane przez indeks GiST.
Ponadto nie ma potrzeby stosowania specjalnych wielkości liter w wartościach NULL. NULL oznacza „otwarty zakres” w typie zakresu – dokładnie to, czego potrzebujemy. Definicja tabeli nie musi się nawet zmieniać:możemy utworzyć typ zakresu w locie - i wesprzeć zapytanie pasującym indeksem wyrażenia:
CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
Powiązane:
- Tabela średniej historii akcji