Saya akan memposting jawaban untuk memulai. Pikiran pertama saya adalah bahwa mungkin untuk mengambil keuntungan dari sifat menjaga urutan loop bersatu bergabung bersama dengan beberapa tabel pembantu yang memiliki satu baris untuk setiap huruf. Bagian yang sulit akan menjadi pengulangan sedemikian rupa sehingga hasilnya dipesan panjang dan menghindari duplikat. Misalnya, ketika bergabung dengan CTE yang mencakup semua 26 huruf kapital bersama dengan '', Anda dapat menghasilkan 'A' + '' + 'A'
dan '' + 'A' + 'A'
yang tentu saja string yang sama.
Keputusan pertama adalah tempat menyimpan data pembantu. Saya mencoba menggunakan tabel temp tetapi ini memiliki dampak negatif yang mengejutkan pada kinerja, meskipun data masuk ke dalam satu halaman. Tabel temp berisi data di bawah ini:
SELECT 'A'
UNION ALL SELECT 'B'
...
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'
Dibandingkan dengan menggunakan CTE, kueri membutuhkan waktu 3X lebih lama dengan tabel berkerumun dan 4X lebih lama dengan heap. Saya tidak percaya masalahnya adalah data ada di disk. Itu harus dibaca ke dalam memori sebagai satu halaman dan diproses dalam memori untuk seluruh paket. Mungkin SQL Server dapat bekerja dengan data dari operator Constant Scan lebih efisien daripada yang dapat dilakukan dengan data yang disimpan di halaman-halaman toko baris biasa.
Menariknya, SQL Server memilih untuk menempatkan hasil yang dipesan dari tabel tempdb halaman tunggal dengan data yang dipesan ke dalam gulungan tabel:
SQL Server sering menempatkan hasil untuk tabel dalam dari gabungan bergabung ke dalam spool tabel, bahkan jika tampaknya tidak masuk akal untuk melakukannya. Saya pikir pengoptimal membutuhkan sedikit pekerjaan di bidang ini. Saya menjalankan kueri dengan NO_PERFORMANCE_SPOOL
untuk menghindari hit kinerja.
Satu masalah dengan menggunakan CTE untuk menyimpan data pembantu adalah bahwa data tidak dijamin dipesan. Saya tidak bisa memikirkan mengapa pengoptimal akan memilih untuk tidak memesannya dan dalam semua pengujian saya data diproses dalam urutan yang saya tulis CTE:
Namun, sebaiknya jangan mengambil risiko apa pun, terutama jika ada cara untuk melakukannya tanpa overhead kinerja yang besar. Dimungkinkan untuk memesan data dalam tabel turunan dengan menambahkan TOP
operator yang berlebihan . Sebagai contoh:
(SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR)
Penambahan kueri itu harus menjamin bahwa hasilnya akan dikembalikan dalam urutan yang benar. Saya berharap semua jenis akan memiliki dampak kinerja negatif yang besar. Pengoptimal kueri juga mengharapkan ini berdasarkan pada perkiraan biaya:
Sangat mengejutkan, saya tidak bisa mengamati perbedaan yang signifikan secara statistik dalam waktu cpu atau runtime dengan atau tanpa pemesanan eksplisit. Jika ada, permintaan tampaknya berjalan lebih cepat dengan ORDER BY
! Saya tidak punya penjelasan untuk perilaku ini.
Bagian rumit dari masalah adalah untuk mencari cara memasukkan karakter kosong ke tempat yang tepat. Seperti disebutkan sebelumnya sederhana CROSS JOIN
akan menghasilkan data rangkap. Kita tahu bahwa string 100000000 akan memiliki panjang enam karakter karena:
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 = 914654 <100000000
tapi
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 + 26 ^ 6 = 321272406> 100000000
Karena itu kita hanya perlu bergabung dengan surat CTE enam kali. Misalkan kita bergabung ke CTE enam kali, ambil satu huruf dari masing-masing CTE, dan menyatukan semuanya. Misalkan huruf paling kiri tidak kosong. Jika salah satu dari huruf berikutnya kosong, itu artinya string tersebut kurang dari enam karakter sehingga merupakan duplikat. Oleh karena itu, kami dapat mencegah duplikat dengan menemukan karakter non-kosong pertama dan memerlukan semua karakter setelah itu juga tidak kosong. Saya memilih untuk melacak ini dengan menetapkan FLAG
kolom ke salah satu CTE dan dengan menambahkan tanda centang pada WHERE
klausa. Ini harus lebih jelas setelah melihat kueri. Kueri terakhir adalah sebagai berikut:
WITH FIRST_CHAR (CHR) AS
(
SELECT 'A'
UNION ALL SELECT 'B'
UNION ALL SELECT 'C'
UNION ALL SELECT 'D'
UNION ALL SELECT 'E'
UNION ALL SELECT 'F'
UNION ALL SELECT 'G'
UNION ALL SELECT 'H'
UNION ALL SELECT 'I'
UNION ALL SELECT 'J'
UNION ALL SELECT 'K'
UNION ALL SELECT 'L'
UNION ALL SELECT 'M'
UNION ALL SELECT 'N'
UNION ALL SELECT 'O'
UNION ALL SELECT 'P'
UNION ALL SELECT 'Q'
UNION ALL SELECT 'R'
UNION ALL SELECT 'S'
UNION ALL SELECT 'T'
UNION ALL SELECT 'U'
UNION ALL SELECT 'V'
UNION ALL SELECT 'W'
UNION ALL SELECT 'X'
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'
)
, ALL_CHAR (CHR, FLAG) AS
(
SELECT '', 0 CHR
UNION ALL SELECT 'A', 1
UNION ALL SELECT 'B', 1
UNION ALL SELECT 'C', 1
UNION ALL SELECT 'D', 1
UNION ALL SELECT 'E', 1
UNION ALL SELECT 'F', 1
UNION ALL SELECT 'G', 1
UNION ALL SELECT 'H', 1
UNION ALL SELECT 'I', 1
UNION ALL SELECT 'J', 1
UNION ALL SELECT 'K', 1
UNION ALL SELECT 'L', 1
UNION ALL SELECT 'M', 1
UNION ALL SELECT 'N', 1
UNION ALL SELECT 'O', 1
UNION ALL SELECT 'P', 1
UNION ALL SELECT 'Q', 1
UNION ALL SELECT 'R', 1
UNION ALL SELECT 'S', 1
UNION ALL SELECT 'T', 1
UNION ALL SELECT 'U', 1
UNION ALL SELECT 'V', 1
UNION ALL SELECT 'W', 1
UNION ALL SELECT 'X', 1
UNION ALL SELECT 'Y', 1
UNION ALL SELECT 'Z', 1
)
SELECT TOP (100000000)
d6.CHR + d5.CHR + d4.CHR + d3.CHR + d2.CHR + d1.CHR
FROM (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d6
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d5
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d4
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d3
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d2
CROSS JOIN (SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR) d1
WHERE (d2.FLAG + d3.FLAG + d4.FLAG + d5.FLAG + d6.FLAG) =
CASE
WHEN d6.FLAG = 1 THEN 5
WHEN d5.FLAG = 1 THEN 4
WHEN d4.FLAG = 1 THEN 3
WHEN d3.FLAG = 1 THEN 2
WHEN d2.FLAG = 1 THEN 1
ELSE 0 END
OPTION (MAXDOP 1, FORCE ORDER, LOOP JOIN, NO_PERFORMANCE_SPOOL);
CTEs seperti dijelaskan di atas. ALL_CHAR
digabung menjadi lima kali karena itu termasuk baris untuk karakter kosong. Karakter terakhir dalam string tidak boleh kosong sehingga CTE terpisah didefinisikan untuk itu FIRST_CHAR
,. Kolom bendera tambahan di ALL_CHAR
digunakan untuk mencegah duplikat seperti dijelaskan di atas. Mungkin ada cara yang lebih efisien untuk melakukan pemeriksaan ini tetapi pasti ada cara yang lebih tidak efisien untuk melakukannya. Satu upaya oleh saya dengan LEN()
dan POWER()
membuat kueri berjalan enam kali lebih lambat dari versi saat ini.
The MAXDOP 1
dan FORCE ORDER
petunjuk penting untuk memastikan bahwa perintah yang diawetkan dalam query. Rencana taksiran beranotasi mungkin membantu untuk mengetahui mengapa gabungan tersebut berada dalam urutan saat ini:
Paket kueri sering dibaca dari kanan ke kiri tetapi permintaan baris terjadi dari kiri ke kanan. Idealnya, SQL Server akan meminta tepat 100 juta baris dari d1
operator pemindaian konstan. Saat Anda bergerak dari kiri ke kanan, saya berharap lebih sedikit baris yang diminta dari masing-masing operator. Kita bisa melihat ini dalam rencana eksekusi yang sebenarnya . Selain itu, di bawah ini adalah tangkapan layar dari SQL Sentry Plan Explorer:
Kami mendapat tepat 100 juta baris dari d1 yang merupakan hal yang baik. Perhatikan bahwa rasio baris antara d2 dan d3 hampir persis 27: 1 (165336 * 27 = 4464072) yang masuk akal jika Anda berpikir tentang cara kerja sambungan silang. Rasio baris antara d1 dan d2 adalah 22,4 yang mewakili beberapa pekerjaan yang sia-sia. Saya percaya baris tambahan berasal dari duplikat (karena karakter kosong di tengah-tengah string) yang tidak membuatnya melewati operator bergabung loop bersarang yang melakukan penyaringan.
The LOOP JOIN
petunjuk teknis yang tidak perlu karena CROSS JOIN
dapat hanya dilaksanakan sebagai loop bergabung dalam SQL Server. The NO_PERFORMANCE_SPOOL
adalah untuk mencegah meja yang tidak perlu spooling. Menghilangkan spool hint membuat kueri membutuhkan waktu 3X lebih lama di mesin saya.
Kueri akhir memiliki waktu cpu sekitar 17 detik dan total waktu berlalu 18 detik. Saat itulah menjalankan kueri melalui SSMS dan membuang set hasil. Saya sangat tertarik melihat metode lain untuk menghasilkan data.