İlk argüman NULL olmasa bile SQL Server bir COALESCE işlevini okuyor mu?


98

COALESCEİlk argümanın çalıştırıldığı zamanların yaklaşık% 95'inde boş olmayacağı bir T-SQL işlevi kullanıyorum . İlk argüman ise NULL, ikinci argüman oldukça uzun bir süreçtir:

SELECT COALESCE(c.FirstName
                ,(SELECT TOP 1 b.FirstName
                  FROM TableA a 
                  JOIN TableB b ON .....)
                )

Örneğin, c.FirstName = 'John'SQL Server hala alt sorguyu çalıştırır mıydı?

VB.NET IIF()işlevini biliyorum , ikinci argüman Doğru ise, kod hala üçüncü argümanı okur (kullanılmasa da).

Yanıtlar:


95

Hayır . İşte basit bir test:

SELECT COALESCE(1, (SELECT 1/0)) -- runs fine
SELECT COALESCE(NULL, (SELECT 1/0)) -- throws error

İkinci koşul değerlendirilirse, sıfıra bölme için bir istisna atılır.

Başına MSDN Belgeler bu nasıl ilişkilidir COALESCEyorumlayıcı tarafından görülüyor - bir yazma sadece kolay bir yoludur CASEdeyimi.

CASE SQL Server'da (çoğunlukla) güvenilir şekilde kısa devreler içeren tek fonksiyonlardan biri olduğu bilinmektedir.

Aaron Bertrand tarafından gösterildiği gibi burada başka bir cevap skaler değişkenler ve toplulaştırmalara karşılaştırarak (ve bu hem de geçerli olacak bazı istisnalar vardır CASEve COALESCE):

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

sıfır hatayla bölme oluşturur.

Bu bir hata olarak düşünülmelidir ve kural COALESCEolarak soldan sağa ayrıştırılır.


6
Lütfen @JNK bu doğru tutmaz nerede çok basit bir durum görmek için cevabım (bkz endişem daha da henüz keşfedilmemiş-senaryolar olmasıdır - ifadesine katılma zor hale CASEhep sol sağa ve her zaman kısa devreleri değerlendirir ).
Aaron Bertrand

4
Diğer ilginç davranış @ SQLKiwi bana işaret etti: SELECT COALESCE((SELECT CASE WHEN RAND() <= 0.5 THEN 1 END), 1);- defalarca tekrarlayın. NULLBazen alacaksın . Tekrar dene ISNULL- asla elde NULL
edemezsin


@ Martin aslında inanıyorum. Ancak, çoğu kullanıcının bu sorunu duymadığı (veya tarafından ısırılmadığı sürece) sezgisel bulduğu davranışları değil.
Aaron Bertrand

73

Peki ya buna - bu bana Jaime Lafargue tarafından söylenen Itzik Ben-Gan tarafından bildirildiği gibi ?

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

Sonuç:

Msg 8134, Level 16, State 1, Line 2
Divide by zero error encountered.

Elbette önemsiz geçici çözümler var, ancak mesele hala soldan sağa değerlendirme / kısa devre yaptırmayı her zaman garanti CASEetmiyor . Burada hatayı bildirdim ve "tasarım tarafından" olarak kapatıldı. Paul White daha sonra bu Connect öğesini dosyaladı ve Fixed olarak kapatıldı. Kendi başlarına çözüldüğü için değil, ancak Books Online'ı, toplamaların bir ifadenin değerlendirme sırasını değiştirebileceği senaryoyu daha doğru bir şekilde açıklayarak güncelledikleri için . Geçenlerde bu konuda daha fazla blog yazdım .CASE

DÜZENLEME sadece Zeyilname, ben bu olduğunu, kenar durumlar olduğunu kabul ederken çoğu zaman sen (belgelerine çelişiyor ve muhtemelen sonunda giderilecektir soldan sağa bunlar değerlendirme ve kısa devre ve bu hataların güvenebilirsiniz bu kesin değil - nedenini görmek için Bart Duncan’ın blog yazısındaki takip eden konuşmaya bakın), millet, bunu ispatlayan tek bir dava olsa bile, her zaman bir şeyin doğru olduğunu söylediğinde aynı fikirde değilim. Eğer Itzik ve diğerleri bu gibi yalnız böcekler bulabilirlerse, en azından başka böceklerin de bulunma ihtimalini mümkün kılar. Ve OP'nin sorgusunun geri kalanını bilmediğimiz için, bu kısa devreye dayanacağına emin olamayacağımızı, ancak bunun tarafından ısırıldığını söyleyemeyiz. Bana göre, daha güvenli bir cevap:

Eğer mümkün olmakla birlikte genellikle itimat CASEsoldan sağa ve kısa devre değerlendirmek için, belgelerinde açıklandığı şekilde, hep bunu yapabilirsiniz demek doğru değildir. Bu sayfada, doğru olmadığı iki kanıtlanmış durum vardır ve hiçbiri SQL Server'ın halka açık sürümlerinde düzeltilmedi.

Buradaki EDITCASE , hiçbir ifadenin bir araya gelmemesine rağmen, bir ifadenin beklediğiniz sırada değerlendirilmediği başka bir durumdur (bunu durdurmam gerekiyor) .



IMO, CASE ifadesinin değerlendirmesinin garanti edilmediğini kanıtlamaz, çünkü toplam değerler seçimden önce hesaplanır (böylece içinde kullanılabilecekleri için).
Salman A

1
@SalmanA Bir CASE ifadesindeki değerlendirme sırasının tam olarak garanti edilmediğini kanıtlamak dışında, bunun muhtemelen başka ne yapabileceğinden emin değilim. Bir istisna alıyoruz çünkü toplam önce hesaplanmış, bir ELSE yan tümcesinde olmasına rağmen - eğer belgelere uyuyorsanız - asla ulaşılmamalı.
Aaron Bertrand

@AaronBertrand toplamları, CASE ifadesinden önce hesaplanır (ve IMO olmalıdır). Gözden geçirilmiş belgeler tam olarak bunu işaret ediyor, hatanın CASE değerlendirilmeden önce ortaya çıktığını gösteriyor .
Salman A

@SalmanA Hâlâ geliştiriciye, CASE ifadesinin yazıldığı sırayla değerlendirmediğini gösteriyor - temeldeki mekanikler, eğer yapmaya çalıştığınız tek şey bir hatanın neden olması gerekmeyen bir CASE dalından geldiğini anlıyorsa önemsiz. t ulaşıldı. Bu sayfadaki diğer tüm örneklerle ilgili argümanlarınız var mı?
Aaron Bertrand

37

Bu konudaki görüşüm, dokümantasyonun niyetinin CASE'in kısa devre yapması gerektiği gerçeğini açıkça ortaya koyduğudur. Aaron'un da belirttiği gibi, bunun her zaman doğru olmadığı gösterilmiş olan birkaç dava (ha!) Olmuştur.

Şimdiye kadar, tüm bunlar hata olarak kabul edildi ve düzeltildi - zorunlu olarak bir SQL Server sürümünde olmasa da bugün satın alabilir ve yama alabilirsiniz (sabit katlama hatası henüz Kümülatif Güncelleme AFAIK'a ulaşmadı). Başlangıçta Itzik Ben-Gan tarafından bildirilen en yeni potansiyel hata henüz araştırılmamıştır (Aaron veya ben kısa süre sonra Connect'e ekleyeceğiz).

Orijinal soruya ilişkin olarak, CASE (ve dolayısıyla COALESCE) ile birlikte yan etki eden fonksiyonların veya alt sorguların kullanıldığı başka sorunlar da var. Düşünmek:

SELECT COALESCE((SELECT CASE WHEN RAND() <= 0.5 THEN 999 END), 999);
SELECT ISNULL((SELECT CASE WHEN RAND() <= 0.5 THEN 999 END), 999);

COALESCE formu sık sık NULL döndürür, https://connect.microsoft.com/SQLServer/feedback/details/546437/coalesce-subquery-1-may-return-null

Optimize edici dönüşümlerle ve ortak ifade izlemeyle gösterilen sorunlar, CASE'in her koşulda kısa devre yapmasını garanti etmenin imkansız olduğu anlamına gelir. Kamu gösteri planı çıktısını inceleyerek bu davranışı tahmin etmenin bile mümkün olamayacağı durumları düşünebilirim, ancak bugün bunun için bir raporum yok.

Özetle, CASE'in genel olarak kısa devre yapacağına (özellikle makul derecede yetenekli bir kişi yürütme planını denetlerse ve bu yürütme planını bir plan rehberi veya ipuçlarıyla 'uygulandıysa'), ancak ihtiyaç duyduğunuzda makul bir şekilde emin olabileceğinizi düşünüyorum. mutlak bir garanti, ifadeyi içermeyen bir SQL yazmanız gerekir.

Sanırım oldukça tatmin edici bir durum değil.


18

Kısa devre yapan CASE/ COALESCEolmayan başka bir davaya rastladım . Aşağıdaki TVF, 1parametre olarak iletilirse PK ihlalini artıracaktır .

CREATE FUNCTION F (@P INT)
RETURNS @T TABLE (
  C INT PRIMARY KEY)
AS
  BEGIN
      INSERT INTO @T
      VALUES      (1),
                  (@P)

      RETURN
  END

Aşağıdaki gibi çağrılırsa

DECLARE @Number INT = 1

SELECT COALESCE(@Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = @Number), 
                         (SELECT TOP (1)  C
                          FROM   F(@Number))) 

Ya da

DECLARE @Number INT = 1

SELECT CASE
         WHEN @Number = 1 THEN @Number
         ELSE (SELECT TOP (1) C
               FROM   F(@Number))
       END 

Her ikisi de sonuç verir

PRIMARY KEY kısıtlaması 'PK__F__3BD019A800551192' ihlali. 'Dbo. @ T' nesnesine kopya anahtar eklenemiyor. Yinelenen anahtar değeri (1).

olduğunu gösteren SELECT(veya en azından masa değişken nüfus) hala yürütülen ve ifadenin o dal ulaşılabilir asla rağmen bir hata tutarsa edilir. COALESCESürüm için plan aşağıdadır.

Plan

Sorgunun yeniden yazılması, sorunu önlemek için görünüyor

SELECT COALESCE(Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = Number), 
                         (SELECT TOP (1)  C
                          FROM   F(Number))) 
FROM (VALUES(1)) V(Number)   

Hangi planı verir

Plan2


8

Başka bir örnek

CREATE TABLE T1 (C INT PRIMARY KEY)

CREATE TABLE T2 (C INT PRIMARY KEY)

INSERT INTO T1 
OUTPUT inserted.* INTO T2
VALUES (1),(2),(3);

Sorgu

SET STATISTICS IO ON;

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (LOOP JOIN)

Hiç bir okumaya karşı gösterir T2.

'Nin arayışı T2yordamın altından geçiyor ve operatör asla idam edilmiyor. Fakat

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (MERGE JOIN)

Okunduğunu gösterir T2. T2Gerçekte hiçbir değere ihtiyaç duyulmasa da aslında hiçbir zaman gerekli değildir.

Tabii ki bu gerçekten şaşırtıcı değil, ancak kısa devre uygulamasının set tabanlı bir bildirici dilde ne anlama geldiği konusunu gündeme getirdiği için karşı örnek deposuna eklenmeye değer olduğunu düşündüm.


7

Sadece düşünmediğiniz bir stratejiden bahsetmek istedim. Burada bir eşleşme olmayabilir, ama bazen işe yarayabilir. Bakalım bu değişiklik size daha iyi performans veriyor mu:

SELECT COALESCE(c.FirstName
            ,(SELECT TOP 1 b.FirstName
              FROM TableA a 
              JOIN TableB b ON .....
              WHERE C.FirstName IS NULL) -- this is the changed part
            )

Bunu yapmanın başka bir yolu da olabilir (temel olarak eşdeğerdir, ancak gerekirse diğer sorgudan daha fazla sütuna erişmenize izin verir):

SELECT COALESCE(c.FirstName, x.FirstName)
FROM
   TableC c
   OUTER APPLY (
      SELECT TOP 1 b.FirstName
      FROM
         TableA a 
         JOIN TableB b ON ...
      WHERE
         c.FirstName IS NULL -- the important part
   ) x

Temel olarak bu, "sert" birleştirme masalarının bir tekniğidir, ancak herhangi bir sıranın ne zaman BİRLEŞTİRİLMESİ gerektiği durumu da içerir. Tecrübelerime göre bu zaman zaman uygulama planlarına gerçekten yardımcı oldu.


3

Hayır olmazdı. Sadece c.FirstNameolduğu zaman çalışırdı NULL.

Ancak, kendin denemelisin. Deney. Sorgunuzun uzun olduğunu söylediniz. Benchmark. Bu konuda kendi sonuçlarını çıkar.

Çalışan alt sorguda @Aaron cevabı daha tamamlandı.

Bununla birlikte, sorgunuzu elden geçirmeniz ve kullanmanız gerektiğini düşünüyorum LEFT JOIN. Çoğu zaman, alt sorgular LEFT JOINs'yi kullanmak için sorgunuzu yeniden işleyerek kaldırılabilir .

Alt sorguları kullanmadaki sorun, genel sorgunuzun yavaş çalışmasıdır çünkü alt sorgu ana sorgunun sonuç kümesindeki her satır için çalıştırılır.


@Adrian hala doğru değil. Uygulama planına bakın ve alt sorguların genellikle akıllıca JOIN'lere dönüştürüldüğünü göreceksiniz. Tüm alt sorgunun her satır için tekrar tekrar çalıştırılması gerektiğini varsaymak, yalnızca bir taramayla birleştirilen bir döngü seçildiğinde etkili bir şekilde gerçekleşebildiğini varsaymak, yalnızca bir düşünce deneyi hatasıdır .
ErikE

3

Gerçek standart, ifadenin veri türünü bir bütün olarak belirlemek için tüm WHEN cümlelerinin (ve ELSE maddesinin yanı sıra) ayrıştırılması gerektiğini söylüyor. Bir hatanın nasıl işlendiğini belirlemek için gerçekten eski notlarımdan bazılarını çıkarmam gerekecekti. Fakat sadece elimizden geldiğinde, 1/0 tam sayı kullanır, bu yüzden bunun bir hata olduğunu varsayardım. Tamsayı veri türünde bir hatadır. Birleştirme listesinde yalnızca boş değerlere sahipseniz, veri türünü belirlemek biraz daha zor olur ve bu başka bir sorundur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.