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