Cevap bölümü
Bunu farklı T-SQL yapıları kullanarak yeniden yazmak için çeşitli yollar vardır. Avantaj ve dezavantajlara bakacağız ve aşağıda genel bir karşılaştırma yapacağız.
İlk önce : kullanmaOR
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;
Kullanmak OR
, bize ihtiyacımız olan satır sayısını tam olarak okuyan daha verimli bir Arama planı verir, ancak teknik dünyanın a whole mess of malarkey
sorgu planına ne dediğini ekler .
Ayrıca, Seek'ın iki kez yürütüldüğünü ve bunun grafiksel operatörden gerçekten daha açık olması gerektiğini de unutmayın:
Table 'Users'. Scan count 2, logical reads 8233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 469 ms, elapsed time = 473 ms.
İkincisi : UNION ALL
Sorgulamamızla türetilmiş tabloları kullanmak da bu şekilde yeniden yazılabilir.
SELECT SUM(Records)
FROM
(
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records);
Bu, daha az malarkey ve aynı şekilde endeksin kaç defa arandığı (aranan?) Konusunda daha belirgin bir dürüstlük derecesiyle aynı plan türünü verir.
OR
Sorgu ile aynı miktarda okur (8233) , ancak 100ms CPU zaman aşımına uğradı.
CPU time = 313 ms, elapsed time = 315 ms.
Bununla birlikte, burada gerçekten dikkatli olmalısınız, çünkü bu plan paralel gitmeye çalışırsa, iki ayrı COUNT
işlem serileştirilecektir, çünkü her biri bir küresel skaler küme olarak kabul edilir. Bayrak 8649'u kullanarak paralel bir planı zorlarsak, sorun ortaya çıkar.
SELECT SUM(Records)
FROM
(
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
Sorgumuzu biraz değiştirerek bu önlenebilir.
SELECT SUM(Records)
FROM
(
SELECT 1
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
Şimdi bir Seek gerçekleştiren her iki düğüm de birleştirme operatörüne ulaşana kadar tamamen paraleldir.
Neye mal olursa olsun, tamamen paralel versiyonun iyi bir faydası var. Yaklaşık 100 okuma maliyeti ve yaklaşık 90 ms ilave CPU zamanı pahasına, geçen süre 93 ms'ye düşer.
Table 'Users'. Scan count 12, logical reads 8317, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 500 ms, elapsed time = 93 ms.
Peki ya CROSS APPLY?
Hiçbir sihir olmadan cevap tamamlanamaz CROSS APPLY
!
Ne yazık ki, biz daha fazla sorunla karşılaşıyoruz COUNT
.
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT COUNT(Id)
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
Bu plan korkunç. Bu, Aziz Patrick Günü'ne en son geldiğiniz zaman planladığınız plan. Güzel paralel olmasına rağmen, bazı nedenlerden dolayı PK / CX'i tarıyor. Ew. Planın 2198 sorgu parası var.
Table 'Users'. Scan count 7, logical reads 31676233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 29532 ms, elapsed time = 5828 ms.
Bu garip bir seçimdir, çünkü kümelenmemiş dizini kullanmaya zorlarsak, maliyet önemli ölçüde 1798 sorgu parasına düşer.
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT COUNT(Id)
FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
Hey, istiyor! Oradan kontrol et. Ayrıca, sihirle, CROSS APPLY
çoğunlukla tamamen paralel bir plana sahip olmak için saçma bir şey yapmamıza gerek olmadığını unutmayın .
Table 'Users'. Scan count 5277838, logical reads 31685303, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 27625 ms, elapsed time = 4909 ms.
Çapraz başvuru, COUNT
oradaki eşyalar olmadan daha iyi sonuç verir .
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT 1
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
Plan iyi görünüyor, ancak okur ve CPU bir gelişme değil.
Table 'Users'. Scan count 20, logical reads 17564, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 4844 ms, elapsed time = 863 ms.
Haçı tekrar yazmak, türetilmiş bir birleşim olmak için geçerlidir, aynı şeyde aynıdır. Sorgu planını ve istatistik bilgilerini yeniden göndermeyeceğim - gerçekten değişmediler.
SELECT COUNT(u.Id)
FROM dbo.Users AS u
JOIN
(
SELECT u.Id
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT u.Id
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x ON x.Id = u.Id;
İlişkisel Cebir : Kapsamlı olmak ve Joe Celko'nun hayallerime dokunmasını engellemek için en azından garip ilişkisel şeyler denemeliyiz. İşte hiçbir şey yok!
Bir girişim INTERSECT
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
INTERSECT
SELECT u.Age WHERE u.Age IS NOT NULL );
Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 1094 ms, elapsed time = 1090 ms.
Ve işte bir girişim EXCEPT
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
EXCEPT
SELECT u.Age WHERE u.Age IS NULL);
Table 'Users'. Scan count 7, logical reads 9247, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 2126 ms, elapsed time = 376 ms.
Bunları yazmanın başka yolları da olabilir, ama bunu belki de kullanan EXCEPT
ve INTERSECT
benden daha sık kullanan insanlara bırakacağım .
Eğer gerçekten sadece bir sayı gerekiyorsa
kullandığım COUNT
steno biraz olarak benim sorgularda (okuyun: Ben daha karmaşık senaryolar bazen ile gelip çok tembelim). Sadece bir sayıma ihtiyacınız varsa, CASE
hemen hemen aynı şeyi yapmak için bir ifade kullanabilirsiniz .
SELECT SUM(CASE WHEN u.Age < 18 THEN 1
WHEN u.Age IS NULL THEN 1
ELSE 0 END)
FROM dbo.Users AS u
SELECT SUM(CASE WHEN u.Age < 18 OR u.Age IS NULL THEN 1
ELSE 0 END)
FROM dbo.Users AS u
Her ikisi de aynı plana sahip ve aynı CPU'ya ve okuma özelliklerine sahip.
Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 719 ms, elapsed time = 719 ms.
Kazanan?
Testlerimde, türetilmiş bir tablonun üzerinde SUM olan zorunlu paralel plan en iyi sonucu verdi. Ve evet, bu sorguların birçoğu her iki öngörüyü de hesaba katan birkaç filtre uygulanmış dizin ekleyerek yardımcı olabilirdi, ancak bazı deneyleri başkalarına bırakmak istedim.
SELECT SUM(Records)
FROM
(
SELECT 1
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
Teşekkürler!
NOT EXISTS ( INTERSECT / EXCEPT )
Sorgular olmadan çalışabilirINTERSECT / EXCEPT
pay:WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );
Başka bir şekilde - kullanırEXCEPT
:SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ;
(kullanıcı kimliği PK ya da herhangi bir özel olmayan boş kolonu (ler)) elde edilmektedir.