Ringkasan
SQL Server menggunakan gabungan yang benar (dalam atau luar) dan menambahkan proyeksi di mana diperlukan untuk menghormati semua semantik dari permintaan asli saat melakukan terjemahan internal antara berlaku dan bergabung .
Perbedaan dalam semua rencana dapat dijelaskan oleh semantik agregat yang berbeda dengan dan tanpa grup dengan klausa dalam SQL Server.
Detail
Bergabunglah vs Terapkan
Kami harus dapat membedakan antara yang berlaku dan yang bergabung :
Menerapkan
Input dalam (bawah) dari penerapan dijalankan untuk setiap baris input luar (atas), dengan satu atau lebih nilai parameter sisi dalam yang disediakan oleh baris luar saat ini. Hasil keseluruhan dari penerapan adalah kombinasi (gabungan semua) dari semua baris yang dihasilkan oleh eksekusi sisi dalam parameter. Kehadiran parameter berarti berlaku kadang-kadang disebut sebagai gabungan berkorelasi.
Sebuah menerapkan selalu diimplementasikan dalam rencana eksekusi oleh Bersarang Loops operator. Operator akan memiliki properti Referensi Luar daripada bergabung dengan predikat. Referensi luar adalah parameter yang dilewatkan dari sisi luar ke sisi dalam pada setiap iterasi loop.
Ikuti
Gabung mengevaluasi predikat gabungnya di operator gabung. Gabung biasanya dapat diimplementasikan oleh Hash Match , Merge , atau Nested Loops operator di SQL Server.
Ketika Nested Loops dipilih, itu dapat dibedakan dari yang diterapkan oleh kurangnya Referensi Luar (dan biasanya kehadiran predikat gabungan). Input bagian dalam gabungan tidak pernah merujuk nilai dari input luar - sisi dalam masih dieksekusi satu kali untuk setiap baris luar, tetapi eksekusi sisi dalam tidak bergantung pada nilai apa pun dari baris luar saat ini.
Untuk lebih jelasnya lihat posting saya Berlaku versus Nested Loops Bergabung .
... mengapa ada bagian luar bergabung dalam rencana eksekusi alih-alih bagian dalam ?
Gabung luar muncul ketika pengoptimal mengubah berlaku untuk bergabung (menggunakan aturan yang disebut ApplyHandler
) untuk melihat apakah itu dapat menemukan rencana berbasis gabung lebih murah. Gabung diperlukan untuk gabung luar untuk kebenaran ketika berlaku mengandung agregat skalar . Gabung dalam tidak akan dijamin untuk menghasilkan hasil yang sama dengan yang asli berlaku seperti yang akan kita lihat.
Agregat Skalar dan Vektor
- Agregat tanpa
GROUP BY
klausa yang sesuai adalah skalar agregat .
- Agregat dengan
GROUP BY
klausa yang sesuai adalah agregat vektor .
Dalam SQL Server, agregat skalar akan selalu menghasilkan baris, bahkan jika tidak diberikan baris untuk agregat. Misalnya, COUNT
agregat skalar tanpa baris adalah nol. Sebuah vektor COUNT
agregat ada baris adalah himpunan kosong (tidak ada baris sama sekali).
Pertanyaan mainan berikut menggambarkan perbedaannya. Anda juga dapat membaca lebih lanjut tentang skalar dan agregat vektor di artikel saya Bersenang-senang dengan Agregat Skalar dan Vektor .
-- Produces a single zero value
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1;
-- Produces no rows
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1 GROUP BY ();
db <> demo biola
Transformasi berlaku untuk bergabung
Saya sebutkan sebelumnya bahwa gabungan harus merupakan gabungan luar untuk kebenaran ketika berlaku asli berisi agregat skalar . Untuk menunjukkan mengapa hal ini terjadi secara terperinci, saya akan menggunakan contoh sederhana dari pertanyaan pertanyaan:
DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);
INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);
SELECT * FROM @A AS A
CROSS APPLY (SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A) AS CA;
Hasil yang benar untuk kolom c
adalah nol , karena COUNT_BIG
merupakan agregat skalar . Ketika menerjemahkan permintaan ini berlaku untuk bergabung dengan formulir, SQL Server menghasilkan alternatif internal yang akan terlihat mirip dengan berikut ini jika dinyatakan dalam T-SQL:
SELECT A.*, c = COALESCE(J1.c, 0)
FROM @A AS A
LEFT JOIN
(
SELECT B.A, c = COUNT_BIG(*)
FROM @B AS B
GROUP BY B.A
) AS J1
ON J1.A = A.A;
Untuk menulis ulang permohonan sebagai gabungan yang tidak berkorelasi, kita harus memperkenalkan GROUP BY
di tabel turunan (jika tidak, tidak ada A
kolom untuk bergabung di). Gabungan harus merupakan gabungan luar sehingga setiap baris dari tabel @A
terus menghasilkan baris dalam output. Gabung kiri akan menghasilkan NULL
kolom untuk c
ketika predikat gabung tidak mengevaluasi ke true. Itu NULL
perlu diterjemahkan ke nol dengan COALESCE
menyelesaikan transformasi yang benar dari berlaku .
Demo di bawah ini menunjukkan bagaimana gabungan luar dan COALESCE
diharuskan untuk menghasilkan hasil yang sama menggunakan gabungan seperti permintaan yang berlaku asli :
db <> demo biola
Dengan GROUP BY
... mengapa membatalkan komentar grup dengan klausa menghasilkan penggabungan batin?
Melanjutkan contoh yang disederhanakan, tetapi menambahkan GROUP BY
:
DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);
INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);
-- Original
SELECT * FROM @A AS A
CROSS APPLY
(SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A GROUP BY B.A) AS CA;
The COUNT_BIG
sekarang menjadi vektor agregat, sehingga hasil yang benar untuk set masukan kosong tidak lagi nol, itu tidak ada baris sama sekali . Dengan kata lain, menjalankan pernyataan di atas tidak menghasilkan output.
Semantik ini jauh lebih mudah untuk dihormati ketika menerjemahkan dari berlaku untuk bergabung , karena CROSS APPLY
secara alami menolak setiap baris luar yang tidak menghasilkan baris sisi dalam. Karena itu, kita dapat menggunakan gabungan dalam dengan aman sekarang, tanpa proyeksi ekspresi tambahan:
-- Rewrite
SELECT A.*, J1.c
FROM @A AS A
JOIN
(
SELECT B.A, c = COUNT_BIG(*)
FROM @B AS B
GROUP BY B.A
) AS J1
ON J1.A = A.A;
Demo di bawah ini menunjukkan bahwa penulisan ulang gabung bagian dalam menghasilkan hasil yang sama seperti yang asli berlaku dengan agregat vektor:
db <> demo biola
Pengoptimal terjadi untuk memilih gabungan gabungan dengan tabel kecil karena menemukan gabungan murah rencana cepat (rencana cukup baik ditemukan). Pengoptimal berbasis biaya dapat melanjutkan untuk menulis ulang penggabungan ke suatu penerapan - mungkin menemukan rencana penerapan yang lebih murah, seperti yang akan terjadi di sini jika loop bergabung atau petunjuk forceseek digunakan - tetapi tidak sepadan dengan usaha dalam kasus ini.
Catatan
Contoh yang disederhanakan menggunakan tabel yang berbeda dengan konten yang berbeda untuk menunjukkan perbedaan semantik lebih jelas.
Orang dapat berargumen bahwa pengoptimal harus dapat beralasan tentang self-join tidak mampu menghasilkan baris yang tidak cocok (non-join), tetapi tidak mengandung logika itu hari ini. Mengakses tabel yang sama beberapa kali dalam permintaan tidak dijamin untuk menghasilkan hasil yang sama secara umum, tergantung pada tingkat isolasi dan aktivitas bersamaan.
Pengoptimal khawatir tentang semantik dan kasus tepi ini sehingga Anda tidak perlu melakukannya.
Bonus: Inner Terapkan Rencana
SQL Server dapat menghasilkan dalam menerapkan rencana (bukan batin bergabung rencana!) Untuk contoh query, itu hanya memilih untuk tidak untuk alasan biaya. Biaya rencana penggabungan luar yang ditunjukkan dalam pertanyaan adalah 0,02898 unit pada contoh SQL Server 2017 laptop saya.
Anda dapat memaksakan rencana yang berlaku (gabungan berkorelasi) menggunakan bendera jejak 9114 yang tidak berdokumen dan tidak didukung (yang menonaktifkan ApplyHandler
dll.) Hanya untuk ilustrasi:
SELECT *
FROM #MyTable AS mt
CROSS APPLY
(
SELECT COUNT_BIG(DISTINCT mt2.Col_B) AS dc
FROM #MyTable AS mt2
WHERE mt2.Col_A = mt.Col_A
--GROUP BY mt2.Col_A
) AS ca
OPTION (QUERYTRACEON 9114);
Ini menghasilkan rencana loop bersarang yang berlaku dengan spool indeks malas. Total perkiraan biaya adalah 0,0463983 (lebih tinggi dari paket yang dipilih):
Perhatikan bahwa rencana eksekusi menggunakan loop bertingkat yang berlaku menghasilkan hasil yang benar menggunakan semantik "gabungan dalam" terlepas dari keberadaan GROUP BY
klausa.
Di dunia nyata, kami biasanya memiliki indeks untuk mendukung pencarian di sisi dalam berlaku untuk mendorong SQL Server untuk memilih opsi ini secara alami, misalnya:
CREATE INDEX i ON #MyTable (Col_A, Col_B);
db <> demo biola