DISTINCT
jest często stosowany do naprawy zapytań, które są zepsute od środka, co często jest powolne i/lub nieprawidłowe. Nie mnóż wierszy na początku, wtedy nie musisz sortować niechcianych duplikatów na końcu.
Łączenie z wieloma n-tabelami ("ma wiele") jednocześnie mnoży wiersze w zestawie wyników. To jak CROSS JOIN
lub produkt kartezjański przez proxy :
- Dwa LEFT JOINS SQL dają niepoprawny wynik
Istnieje wiele sposobów uniknięcia tego błędu.
Najpierw agreguj, dołącz później
Z technicznego punktu widzenia zapytanie działa tak długo, jak dołączysz do jednego tabela z wieloma wierszami naraz przed agregacją:
SELECT e.id, e.name, e.age, e.streets, arrag_agg(wd.day) AS days
FROM (
SELECT e.id, e.name, e.age, array_agg(ad.street) AS streets
FROM employees e
JOIN address ad ON ad.employeeid = e.id
GROUP BY e.id -- id enough if it is defined PK
) e
JOIN workingdays wd ON wd.employeeid = e.id
GROUP BY e.id, e.name, e.age;
Najlepiej też podać klucz podstawowy id
i GROUP BY
to, ponieważ name
i age
niekoniecznie są wyjątkowe. Możesz przez pomyłkę połączyć dwóch pracowników.
Możesz jednak dokonać agregacji w podzapytaniu przed dołączasz, to jest lepsze, chyba że masz selektywne WHERE
warunki dotyczące employees
:
SELECT e.id, e.name, e.age, ad.streets, arrag_agg(wd.day) AS days
FROM employees e
JOIN (
SELECT employeeid, array_agg(ad.street) AS streets
FROM address
GROUP BY 1
) ad ON ad.employeeid = e.id
JOIN workingdays wd ON e.id = wd.employeeid
GROUP BY e.id, e.name, e.age, ad.streets;
Lub agreguj oba:
SELECT name, age, ad.streets, wd.days
FROM employees e
JOIN (
SELECT employeeid, array_agg(ad.street) AS streets
FROM address
GROUP BY 1
) ad ON ad.employeeid = e.id
JOIN (
SELECT employeeid, arrag_agg(wd.day) AS days
FROM workingdays
GROUP BY 1
) wd ON wd.employeeid = e.id;
Ostatni z nich jest zazwyczaj szybszy, jeśli odzyskasz wszystkie lub większość wierszy w tabelach podstawowych.
Zauważ, że używając JOIN
a nie LEFT JOIN
usuwa z wyniku pracowników, którzy nie mają adresu lub brak dni roboczych. To może być zamierzone lub nie. Przełącz na LEFT JOIN
zachować wszystkie pracowników w wyniku.
Skorelowane podzapytania / łączenie BOCZNE
Dla małego wyboru , zamiast tego rozważyłbym skorelowane podzapytania:
SELECT name, age
, (SELECT array_agg(street) FROM address WHERE employeeid = e.id) AS streets
, (SELECT arrag_agg(day) FROM workingdays WHERE employeeid = e.id) AS days
FROM employees e
WHERE e.namer = 'peter'; -- very selective
Lub, w Postgresie 9.3 lub nowszym, możesz użyć LATERAL
dołącza do tego:
SELECT e.name, e.age, a.streets, w.days
FROM employees e
LEFT JOIN LATERAL (
SELECT array_agg(street) AS streets
FROM address
WHERE employeeid = e.id
GROUP BY 1
) a ON true
LEFT JOIN LATERAL (
SELECT array_agg(day) AS days
FROM workingdays
WHERE employeeid = e.id
GROUP BY 1
) w ON true
WHERE e.name = 'peter'; -- very selective
- Jaka jest różnica między LATERAL a podzapytanie w PostgreSQL?
Każde zapytanie zachowuje wszystkie pracowników w wyniku.