Datang ke SQL dari bahasa pemrograman lain, struktur kueri rekursif terlihat agak aneh. Berjalan melaluinya langkah demi langkah, dan tampaknya berantakan.
Perhatikan contoh sederhana berikut ini:
CREATE TABLE #NUMS
(N BIGINT);
INSERT INTO #NUMS
VALUES (3), (5), (7);
WITH R AS
(
SELECT N FROM #NUMS
UNION ALL
SELECT N*N AS N FROM R WHERE N*N < 10000000
)
SELECT N FROM R ORDER BY N;
Mari kita jalani saja.
Pertama, anggota anchor dieksekusi dan set hasil dimasukkan ke dalam R. Jadi R diinisialisasi ke {3, 5, 7}.
Kemudian, eksekusi turun di bawah UNION ALL dan anggota rekursif dieksekusi untuk pertama kalinya. Ini dijalankan pada R (yaitu, pada R yang saat ini kami miliki: {3, 5, 7}). Ini menghasilkan {9, 25, 49}.
Apa hubungannya dengan hasil baru ini? Apakah itu menambahkan {9, 25, 49} ke {3, 5, 7} yang ada, melabelkan gabungan R yang dihasilkan, dan kemudian melanjutkan dengan rekursi dari sana? Atau apakah itu mendefinisikan ulang R menjadi hanya hasil baru ini {9, 25, 49} dan melakukan semua penyatuan nanti?
Tidak ada pilihan yang masuk akal.
Jika R sekarang {3, 5, 7, 9, 25, 49} dan kami menjalankan iterasi rekursi berikutnya, maka kami akan berakhir dengan {9, 25, 49, 81, 625, 2401} dan kami telah hilang {3, 5, 7}.
Jika R sekarang hanya {9, 25, 49}, maka kami memiliki masalah pemberian label yang salah. R dipahami sebagai gabungan dari himpunan hasil anggota jangkar dan semua set hasil anggota rekursif berikutnya. Sedangkan {9, 25, 49} hanya komponen R. Ini bukan R penuh yang telah kami dapatkan sejauh ini. Oleh karena itu, menulis anggota rekursif sebagai memilih dari R tidak masuk akal.
Saya tentu menghargai apa yang telah dijelaskan oleh @Max Vernon dan @Michael S. Yaitu, bahwa (1) semua komponen dibuat hingga batas rekursi atau set nol, dan kemudian (2) semua komponen disatukan. Ini adalah bagaimana saya memahami rekursi SQL untuk benar-benar berfungsi.
Jika kami mendesain ulang SQL, mungkin kami akan menerapkan sintaks yang lebih jelas dan eksplisit, seperti ini:
WITH R AS
(
SELECT N
INTO R[0]
FROM #NUMS
UNION ALL
SELECT N*N AS N
INTO R[K+1]
FROM R[K]
WHERE N*N < 10000000
)
SELECT N FROM R ORDER BY N;
Semacam bukti induktif dalam matematika.
Masalah dengan rekursi SQL seperti saat ini adalah bahwa itu ditulis dengan cara yang membingungkan. Cara itu ditulis mengatakan bahwa setiap komponen dibentuk dengan memilih dari R, tetapi itu tidak berarti R penuh yang telah (atau, tampaknya telah) dibangun sejauh ini. Itu hanya berarti komponen sebelumnya.
sumber
Jawaban:
Deskripsi BOL dari CTE rekursif menggambarkan semantik eksekusi rekursif adalah sebagai berikut:
Jadi masing-masing level hanya memiliki input level di atas, bukan seluruh set hasil yang terakumulasi sejauh ini.
Di atas adalah cara kerjanya secara logis . CTE rekursif fisik saat ini selalu diimplementasikan dengan loop bersarang dan tumpukan gulungan di SQL Server. Ini dijelaskan di sini dan di sini dan berarti bahwa dalam praktiknya setiap elemen rekursif hanya bekerja dengan baris induk dari level sebelumnya, bukan seluruh level. Tetapi berbagai batasan pada sintaks yang diijinkan dalam CTE rekursif berarti pendekatan ini berfungsi.
Jika Anda menghapus
ORDER BY
dari kueri, hasilnya akan dipesan sebagai berikutIni karena rencana eksekusi beroperasi sangat mirip dengan yang berikut ini
C#
NB1: Seperti di atas pada saat anak pertama dari anggota jangkar
3
sedang diproses semua informasi tentang saudara kandungnya,5
dan7
, dan keturunan mereka, telah dibuang dari kumparan dan tidak lagi dapat diakses.NB2: C # di atas memiliki semantik keseluruhan yang sama dengan rencana eksekusi tetapi aliran dalam rencana eksekusi tidak identik, karena di sana para operator bekerja dengan cara exection pipeline. Ini adalah contoh yang disederhanakan untuk menunjukkan inti dari pendekatan. Lihat tautan sebelumnya untuk detail lebih lanjut tentang rencana itu sendiri.
NB3: Spool stack itu sendiri tampaknya diimplementasikan sebagai indeks cluster tidak unik dengan kolom kunci tingkat rekursi dan unikifiers ditambahkan sesuai kebutuhan ( sumber )
sumber
IterateToDepthFirst
-Iterate(seed,rcsv)->PhysIterate(seed,rcsv)
. Hanya FYI. Jawaban yang sangat bagus.Ini hanya tebakan (semi) berpendidikan, dan mungkin sepenuhnya salah. Pertanyaan yang menarik.
T-SQL adalah bahasa deklaratif; mungkin CTE rekursif diterjemahkan ke dalam operasi gaya kursor di mana hasil dari sisi kiri UNION ALL ditambahkan ke tabel sementara, maka sisi kanan UNION ALL diterapkan ke nilai-nilai di sisi kiri.
Jadi, pertama kita masukkan output dari sisi kiri UNION ALL ke dalam set hasil, kemudian kita masukkan hasil dari sisi kanan UNION ALL yang diterapkan ke sisi kiri, dan masukkan itu ke dalam set hasil. Sisi kiri kemudian diganti dengan output dari sisi kanan, dan sisi kanan diterapkan lagi ke sisi kiri "baru". Sesuatu seperti ini:
Anda dapat melihat perilaku ini dalam rencana eksekusi untuk CTE rekursif:
Ini adalah langkah 1 di atas, di mana sisi kiri UNION ALL ditambahkan ke output:
Ini adalah sisi kanan UNION ALL di mana output digabungkan ke set hasil:
sumber
The SQL Server dokumentasi , yang menyebutkan T i dan T i + 1 , yang tidak sangat dimengerti, atau deskripsi akurat tentang pelaksanaan yang sebenarnya.
Ide dasarnya adalah bahwa bagian rekursif dari kueri melihat semua hasil sebelumnya, tetapi hanya sekali .
Mungkin bermanfaat untuk melihat bagaimana database lain menerapkan ini (untuk mendapatkan hasil yang sama ). The dokumentasi Postgres mengatakan:
The dokumentasi SQLite petunjuk pada implementasi yang sedikit berbeda, dan ini algoritma satu-baris-di-a-waktu mungkin yang paling mudah untuk memahami:
sumber
Pengetahuan saya ada di DB2 khusus tetapi melihat diagram menjelaskan tampaknya sama dengan SQL Server.
Rencananya berasal dari sini:
Lihat di Tempel Paket
Pengoptimal tidak benar-benar menjalankan gabungan semua untuk setiap kueri rekursif. Dibutuhkan struktur kueri dan menetapkan bagian pertama dari serikat semua untuk "anggota jangkar" maka akan berjalan melalui bagian kedua dari serikat semua (disebut "anggota rekursif" secara rekursif sampai mencapai batas yang ditentukan. Setelah rekursi selesai, optimizer menggabungkan semua catatan bersama.
Pengoptimal hanya menganggapnya sebagai saran untuk melakukan operasi yang ditentukan sebelumnya.
sumber