Bir satırın varlığını kontrol etmek zorunda kaldığımı sık sık okudum, her zaman bir COUNT yerine EXISTS ile yapılmalı.
Bir şeyin her zaman gerçek olması, özellikle de veritabanlarına gelince, çok nadir görülür . SQL'de aynı semantiği ifade etmenin herhangi bir yolu vardır. Yararlı bir kural varsa, mevcut en doğal sözdizimini kullanarak sorguları yazmak (ve evet, özneldir) ve yalnızca aldığınız sorgu planı veya performans kabul edilemezse, yeniden yazmayı düşünebilirsiniz.
Buna değer, benim kendi meselemdeki değeri, varoluş sorguları en doğal biçimde kullanılarak ifade ediliyor EXISTS
. Bu da benim deneyim oldu EXISTS
daha iyi optimize eğilimindedir daha OUTER JOIN
reddetmek NULL
alternatif. Kullanmak COUNT(*)
ve filtrelemek =0
başka bir alternatiftir, bu da SQL Server sorgu iyileştiricisinde bir miktar desteğe sahip olmakla birlikte, bunu kişisel olarak daha karmaşık sorgularda güvenilmez olarak buldum. Her durumda, EXISTS
bu alternatiflerin ikisinden de (benim için) çok daha doğal görünüyor.
EXISTS ile yaptığım ölçümlere mükemmel bir anlam kazandıran kayda değer bir kusur olup olmadığını merak ediyordum.
Özel örneğiniz ilginçtir, çünkü optimize edicinin CASE
ifadelerdeki alt sorgular ile ilgilenme biçimini (ve EXISTS
özellikle de testleri) vurgular .
CASE ifadelerindeki alt sorgular
Aşağıdaki (tamamen yasal) sorguyu göz önünde bulundurun:
DECLARE @Base AS TABLE (a integer NULL);
DECLARE @When AS TABLE (b integer NULL);
DECLARE @Then AS TABLE (c integer NULL);
DECLARE @Else AS TABLE (d integer NULL);
SELECT
CASE
WHEN (SELECT W.b FROM @When AS W) = 1
THEN (SELECT T.c FROM @Then AS T)
ELSE (SELECT E.d FROM @Else AS E)
END
FROM @Base AS B;
SemantiğininCASE
bu olan WHEN/ELSE
maddeleri olan , genel olarak metin için değerlendirilmiştir. Yukarıdaki ELSE
sorguda, WHEN
yan tümce birden fazla satır döndürdüyse, yan tümce tatmin olmuşsa, SQL Server'ın bir hata döndürmesi yanlış olur . Bu semantiklere saygı duymak için, optimizer doğrudan geçiş tahminlerini kullanan bir plan üretir:
Yuvalanmış ilmeğin birleştirilmesinin iç tarafı sadece geçiş belirteci yanlış döndüğünde değerlendirilir. Genel etki, CASE
ifadelerin sırayla test edilmesi ve alt sorguların yalnızca önceki ifadelerin karşılanmaması durumunda değerlendirilmesidir.
EXISTS alt sorgusu ile CASE ifadeleri
Bir CASE
alt sorgunun kullanıldığı yerlerde EXISTS
, mantıksal varlık testi yarı birleştirme olarak uygulanır, ancak daha sonra bir cümlenin gerekmesi durumunda normalde yarı birleşme tarafından reddedilecek satırlar korunmalıdır. Bu özel tür bir yarı birleşimden geçen satırlar, yarı birleşimin bir eşleşme bulup bulamadığını gösteren bir bayrak alır. Bu bayrak prob sütunu olarak bilinir .
Uygulamanın ayrıntıları, mantıksal alt sorgunun bir sonda sütunu ile ilişkili bir birleşme ('uygula') ile değiştirilmiş olmasıdır. Çalışma, sorgu en iyi duruma RemoveSubqInPrj
getiricisi adlı bir basitleştirme kuralı tarafından gerçekleştirilir (projeksiyondaki alt sorguyu kaldır). Ayrıntıları 8606 izleme bayrağı kullanarak görebiliriz:
SELECT
T1.ID,
CASE
WHEN EXISTS
(
SELECT 1
FROM #T2 AS T2
WHERE T2.ID = T1.ID
) THEN 1
ELSE 0
END AS DoesExist
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8606);
EXISTS
Testi gösteren giriş ağacının bir kısmı aşağıda gösterilmiştir:
ScaOp_Exists
LogOp_Project
LogOp_Select
LogOp_Get TBL: #T2
ScaOp_Comp x_cmpEq
ScaOp_Identifier [T2].ID
ScaOp_Identifier [T1].ID
Bu, aşağıdakiler tarafından RemoveSubqInPrj
yönetilen bir yapıya dönüştürülür :
LogOp_Apply (x_jtLeftSemi probe PROBE:COL: Expr1008)
Bu, daha önce tarif edilen probla yapılan sol yarı birleştirme uygulamasıdır. Bu ilk dönüştürme, SQL Server sorgu en iyi duruma getiricilerinde şu ana kadar kullanılabilir olan tek şeydir ve bu dönüştürme devre dışı bırakıldığında derleme başarısız olur.
Bu sorgu için olası yürütme planı şekillerinden biri, bu mantıksal yapının doğrudan uygulanmasıdır:
Son Hesaplama Ölçeği CASE
, sonda sütun değerini kullanan ifadenin sonucunu değerlendirir :
Optimize, yarı birleştirme için diğer fiziksel birleşme türlerini göz önüne aldığında, plan ağacının temel şekli korunur. Yalnızca birleştirme birleştirme bir prob sütununu destekler, bu nedenle mantıksal olarak mümkün olsa da bir karma yarı birleştirme dikkate alınmaz:
Birleştirme Expr1008
, plandaki herhangi bir operatörde bunun için bir tanım görünmemesine rağmen etiketin (önce ismiyle aynı olduğu gibi) etiketli bir ifade çıktığını fark eder. Bu yine sadece sonda sütunu. Daha önce olduğu gibi, nihai Hesaplama Skaleri, bu sonda değerini değerlendirmek için kullanır CASE
.
Sorun, optimizer’in, yalnızca birleştirme (veya karma) yarı birleştirme ile kayda değer hale gelen alternatifleri tam olarak keşfetmemesidir. İç içe döngüler planında, satırların T2
her yinelemedeki aralıkla eşleşip eşleşmediğini kontrol etmenin bir avantajı yoktur . Bir birleştirme veya karma planla birlikte, bu yararlı bir optimizasyon olabilir.
Sorguya bir eşleşme BETWEEN
belirteci eklersek T2
, gerçekleşen tek şey bu denetimin her satır için birleştirme yarı birleşiminde artık olarak gerçekleştirilmesidir (yürütme planında tespit edilmesi zor, ancak işte):
SELECT
T1.ID,
CASE
WHEN EXISTS
(
SELECT 1
FROM #T2 AS T2
WHERE T2.ID = T1.ID
AND T2.ID BETWEEN 5000 AND 7000 -- New
) THEN 1
ELSE 0
END AS DoesExist
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000;
Umarız bunun BETWEEN
yerine T2
arama ile sonuçlanacak şekilde aşağı itilir . Normalde, optimizer bunu yapmayı düşünür (sorguda fazladan bir işaret olmadan bile). Bu tanıdığı zımni yüklemleri ( BETWEEN
üzerinde T1
ve arasında yüklemi katılmak T1
ve T2
birlikte ima BETWEEN
üzerinde T2
) onlara orijinal sorgu metninde mevcut olmadan. Ne yazık ki, başvuru-probu modeli bunun keşfedilmediği anlamına gelir.
Her iki girişte arama üretmek için sorguyu birleştirme yarı birleşimine yazmak için yollar vardır. Bir yol, sorguyu oldukça doğal olmayan bir şekilde yazmayı içerir (genellikle tercih ettiğim nedeni yitirir EXISTS
):
WITH T2 AS
(
SELECT TOP (9223372036854775807) *
FROM #T2 AS T2
WHERE ID BETWEEN 5000 AND 7000
)
SELECT
T1.ID,
DoesExist =
CASE
WHEN EXISTS
(
SELECT * FROM T2
WHERE T2.ID = T1.ID
) THEN 1 ELSE 0 END
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000;
Bu sorguyu bir üretim ortamında yazmaktan mutlu olmazdım, sadece istenen plan şeklinin mümkün olduğunu göstermek için. Yazmanız gereken gerçek sorgu CASE
, bu şekilde kullanıyorsa ve performans, bir birleştirme yarı birleşiminin prob tarafında bir arama yapılmadığı için sıkıntı çekiyorsa, sorguyu doğru sonuçları üreten farklı bir sözdizimi kullanarak yazmayı düşünebilirsiniz. daha verimli yürütme planı.