Başlamak için bir cevap göndereceğim. İlk düşüncem, her harf için bir sıra olan birkaç yardımcı tablo ile birlikte iç içe bir döngü birleşiminin sipariş koruma doğasından faydalanabilmem gerektiğiydi. Zor kısım, sonuçlar uzunluğa göre sıralanacak ve kopyalardan kaçınacak şekilde ilmekleyecekti. Örneğin, '' ile birlikte 26 büyük harf içeren bir CTE'yi çapraz olarak birleştirirken, sonuçta 'A' + '' + 'A'
ve '' + 'A' + 'A'
elbette aynı dize olan sonuç elde edebilirsiniz .
İlk karar yardımcı verilerin nerede saklanacağıydı. Bir geçici tablo kullanmayı denedim, ancak veriler tek bir sayfaya sığsa bile, performans üzerinde şaşırtıcı derecede olumsuz bir etkisi oldu. Geçici tablo aşağıdaki verileri içeriyordu:
SELECT 'A'
UNION ALL SELECT 'B'
...
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'
CTE kullanımıyla karşılaştırıldığında, sorgu kümelenmiş bir tabloyla 3 kat ve bir yığınla 4 kat daha uzun sürdü. Sorunun verinin disk üzerinde olduğuna inanmıyorum. Hafızaya tek bir sayfa olarak okunmalı ve tüm plan için hafızada işlenmelidir. SQL Server, bir Sabit Tarama operatörünün verileriyle, tipik satır mağaza sayfalarında depolanan verilerle olduğundan daha verimli çalışabilir.
İlginç bir şekilde, SQL Server sipariş edilen verileri içeren tek bir sayfa tempdb tablosundan sipariş edilen sonuçları bir tablo makarasına koymayı seçer:
SQL Server, çapraz birleştirmenin iç tablosu için sonuçları, bunu yapmak saçma gibi görünse bile, bir tablo makarasına koyar. Optimizer'ın bu alanda biraz çalışmaya ihtiyacı olduğunu düşünüyorum. Ben NO_PERFORMANCE_SPOOL
performans isabet önlemek için ile sorgu çalıştırdı .
Yardımcı verileri depolamak için CTE kullanmanın bir sorunu, verilerin sipariş edileceğinin garanti edilmemesidir. Optimize edicinin neden sipariş vermemeyi seçtiğini düşünemiyorum ve tüm testlerimde veriler CTE'yi yazdığım sırada işlendi:
Ancak, özellikle büyük bir performans yükü olmadan bunu yapmanın bir yolu varsa, herhangi bir şans almamanız en iyisidir. Gereksiz bir TOP
operatör ekleyerek türetilmiş bir tablodaki verileri sipariş etmek mümkündür . Örneğin:
(SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR)
Sorguya eklenmesi, sonuçların doğru sırada döndürülmesini garanti etmelidir. Her türlü performansın olumsuz bir etkisi olmasını bekliyordum. Sorgu optimize edici de tahmini maliyetlere göre bunu bekledi:
Çok şaşırtıcı bir şekilde, cpu zamanı veya çalışma süresinde açık siparişle veya sipariş olmadan istatistiksel olarak anlamlı bir fark gözlemleyemedim. Bir şey varsa, sorgu ile daha hızlı çalışıyor gibiydi ORDER BY
! Bu davranış için hiçbir açıklamam yok.
Sorunun zor kısmı, boş karakterlerin doğru yerlere nasıl ekleneceğini bulmaktı. Daha önce de belirtildiği gibi, basit bir CROSS JOIN
veri yinelenir. 100000000. karakter dizisinin altı karakter uzunluğunda olacağını biliyoruz çünkü:
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 = 914654 <100000000
fakat
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 + 26 ^ 6 = 321272406> 100000000
Bu nedenle CTE harfine sadece altı kez katılmamız gerekir. CTE'ye altı kez katıldığımızı, her CTE'den bir mektup aldığımızı ve hepsini bir araya getirdiğimizi varsayalım. En soldaki harfin boş olmadığını varsayalım. Sonraki harflerden herhangi biri boşsa, dizenin altı karakterden daha kısa olduğu ve yinelenen bir harf olduğu anlamına gelir. Bu nedenle, ilk boş olmayan karakteri bularak ve boş kalmadan sonra tüm karakterleri isteyerek yinelemeleri önleyebiliriz. FLAG
CTE'lerden birine bir sütun atayarak ve WHERE
maddeye bir onay ekleyerek bunu izlemeyi seçtim . Sorguya baktıktan sonra bu daha açık olmalıdır. Son sorgu aşağıdaki gibidir:
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);
CTE'ler yukarıda tarif edildiği gibidir. ALL_CHAR
boş bir karakter için bir satır içerdiğinden beş kez birleştirilir. Dizedeki son karakter hiçbir zaman boş bırakılmamalı, bu nedenle onun için ayrı bir CTE tanımlanmalıdır FIRST_CHAR
. İçindeki ekstra bayrak sütunu, ALL_CHAR
yukarıda açıklandığı gibi kopyaları önlemek için kullanılır. Bu kontrolü yapmanın daha etkili bir yolu olabilir, ancak bunu yapmanın kesinlikle daha verimsiz yolları vardır. One benim tarafımdan girişimi LEN()
ve POWER()
güncel sürümü altı kat daha yavaş sorgu yolculuğuna devam etti.
MAXDOP 1
Ve FORCE ORDER
ipuçları emin sipariş sorguda korunduğunu yapmak esastır. Açıklamalı bir tahmini plan, birleştirmelerin neden mevcut sıralarında olduklarını görmek için yardımcı olabilir:
Sorgu planları genellikle sağdan sola okunur, ancak satır istekleri soldan sağa olur. İdeal olarak, SQL Server d1
sabit tarama operatörünün tam 100 milyon satırını isteyecektir . Soldan sağa doğru hareket ettikçe, her operatörden daha az satır istenmesini bekliyorum. Bunu gerçek uygulama planında görebiliriz . Ayrıca, aşağıda SQL Sentry Plan Explorer'ın bir ekran görüntüsü yer almaktadır:
D1'den tam olarak 100 milyon satır aldık, bu iyi bir şey. Çapraz birleştirmenin nasıl çalışacağını düşünüyorsanız, d2 ve d3 arasındaki satırların oranının neredeyse 27: 1 (165336 * 27 = 4464072) olduğuna dikkat edin. D1 ve d2 arasındaki satırların oranı 22.4'tür ve bu da boşa harcanmış bir işi temsil eder. Ben ekstra satırları filtreleme yapan iç içe döngü birleştirme operatörü geçmiş yapmak değil (dizelerin ortasında boş karakterler nedeniyle) yinelenen olduğuna inanıyorum.
LOOP JOIN
Bir nedeni ipucu teknik olarak gereksiz CROSS JOIN
sadece bir döngü olarak uygulanacak kutu SQL Server katılmak. Bu NO_PERFORMANCE_SPOOL
gereksiz masa biriktirmesini önlemek içindir. Biriktirme ipucunu atlamak sorguyu makinemde 3 kat daha uzun sürdü.
Son sorgunun işlemci süresi yaklaşık 17 saniye ve toplam geçen süresi 18 saniyedir. Bu, sorguyu SSMS aracılığıyla çalıştırıp sonuç kümesini atarken oldu. Verileri üretmenin diğer yöntemlerini görmekle çok ilgileniyorum.