DISTINCT
sering diterapkan untuk memperbaiki kueri yang busuk dari dalam, dan itu sering kali lambat dan/atau salah. Jangan mengalikan baris untuk memulai, maka Anda tidak perlu memilah duplikat yang tidak diinginkan di akhir.
Bergabung ke beberapa n-tabel ("memiliki banyak") sekaligus mengalikan baris dalam kumpulan hasil. Itu seperti CROSS JOIN
atau produk Cartesian melalui proxy :
- Dua SQL LEFT JOIN menghasilkan hasil yang salah
Ada berbagai cara untuk menghindari kesalahan ini.
Agregat dulu, gabung nanti
Secara teknis, kueri berfungsi selama Anda bergabung ke satu tabel dengan beberapa baris sekaligus sebelum Anda menggabungkan:
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;
Sebaiknya sertakan kunci utama id
dan GROUP BY
itu, karena name
dan age
belum tentu unik. Anda dapat menggabungkan dua karyawan secara tidak sengaja.
Tetapi Anda dapat menggabungkan dalam subkueri sebelum Anda bergabung, itu lebih baik kecuali Anda memilih WHERE
ketentuan pada 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;
Atau gabungkan keduanya:
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;
Yang terakhir biasanya lebih cepat jika Anda mengambil semua atau sebagian besar dari baris dalam tabel dasar.
Perhatikan bahwa menggunakan JOIN
dan bukan LEFT JOIN
menghapus karyawan dari hasil yang tidak memiliki alamat atau tidak ada hari kerja. Itu mungkin atau mungkin tidak dimaksudkan. Beralih ke LEFT JOIN
untuk mempertahankan semua karyawan dalam hasilnya.
Subkueri terkait / LATERAL bergabung
Untuk pilihan kecil , saya akan mempertimbangkan subkueri yang berkorelasi sebagai gantinya:
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
Atau, dengan Postgres 9.3 atau yang lebih baru, Anda dapat menggunakan LATERAL
bergabung untuk itu:
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
- Apa perbedaan antara LATERAL dan subquery di PostgreSQL?
Salah satu kueri mempertahankan semua karyawan dalam hasilnya.