Exists 1 veya Exists kullanarak alt sorgu *


89

EXISTS çeklerimi şöyle yazardım:

IF EXISTS (SELECT * FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Where Columns=@Filters
END

Önceki yaşamdaki DBA'lardan biri bana bir EXISTScümle yaptığımda SELECT 1yerine şunu kullanacağını söylediSELECT *

IF EXISTS (SELECT 1 FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Columns=@Filters
END

Bu gerçekten bir fark yaratır mı?


1
VAROLARI unuttunuz (BOŞ SEÇİN ...). Bu kısa süre önce soruldu
OMG Ponies

17
ps yeni bir DBA alır. Batıl
inancın BT'de

Yanıtlar:


136

Hayır, SQL Server akıllıdır ve bir MEVCUT için kullanıldığını bilir ve sisteme NO DATA döndürür.

Quoth Microsoft: http://technet.microsoft.com/en-us/library/ms189259.aspx?ppud=4

EXISTS tarafından sunulan bir alt sorgunun seçim listesi neredeyse her zaman bir yıldız işaretinden (*) oluşur. Sütun adlarını listelemek için bir neden yoktur, çünkü yalnızca alt sorguda belirtilen koşulları karşılayan satırların var olup olmadığını test ediyorsunuz.

Kendinizi kontrol etmek için aşağıdakileri çalıştırmayı deneyin:

SELECT whatever
  FROM yourtable
 WHERE EXISTS( SELECT 1/0
                 FROM someothertable 
                WHERE a_valid_clause )

Aslında SELECT listesiyle bir şey yapıyor olsaydı, sıfır hata ile bir div atardı. Öyle değil.

DÜZENLEME: Dikkat edin, SQL Standard aslında bundan bahsediyor.

ANSI SQL 1992 Standard, pg 191 http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

3) Durum:
a) Eğer <select list>"*" basitçe <subquery> , hemen bir içinde bulunan bir a içinde yer alıyorsa <exists predicate>, o zaman keyfi olan a'ya <select list> eşdeğerdir .<value expression><literal>


1
EXISTS1/0 ile hile bile bu kadar uzatılabilir SELECT 1 WHERE EXISTS(SELECT 1/0)ikinci olarak ... sonra adım daha soyut görünüyor SELECThayır vardır FROMmaddesini
whytheq

1
@whytheq - Veya SELECT COUNT(*) WHERE EXISTS(SELECT 1/0). Bir SELECTbir olmadan FROMo (gelen seçerek örneğin benzer tek bir satır tablo erişen sanki SQL Server tedavi edilir dualdiğer RDBMSs tablonun)
Martin Smith

@MartinSmith tezahürat yapıyor - yani önemli olan SELECT, başka bir şey yapmadan önce 1 satırlık bir tablo oluşturması , yani 1 satırlık 1/0tablo hala çöp olsa bile EXISTS?
whytheq

Bu her zaman böyle miydi, yoksa SQL Server'ın belirli bir sürümünde sunulan bir optimizasyon mu?
Martin Brown

1
@MartinSmith TIL "quoth". Düzeltdiğiniz için teşekkürler.
Gurwinder Singh

114

Bu yanlış anlamanın nedeni muhtemelen tüm yazıları okumaya son vereceği inancından kaynaklanıyor. Durumun böyle olmadığını görmek kolay.

CREATE TABLE T
(
X INT PRIMARY KEY,
Y INT,
Z CHAR(8000)
)

CREATE NONCLUSTERED INDEX NarrowIndex ON T(Y)

IF EXISTS (SELECT * FROM T)
    PRINT 'Y'

Plan verir

Plan

Bu, SQL Server'ın, dizinin tüm sütunları içermemesine rağmen sonucu kontrol etmek için mevcut en dar dizini kullanabildiğini gösterir. İndeks erişimi yarı birleştirme operatörü altındadır, bu da ilk satır döndürüldüğünde taramayı durdurabileceği anlamına gelir.

Yani yukarıdaki inancın yanlış olduğu açıktır.

Bununla birlikte, Sorgu İyileştirici ekibinden Conor Cunningham, burada tipik SELECT 1olarak bu durumda kullandığını açıklıyor çünkü bu , sorgu derlemesinde küçük bir performans farkı yaratabilir .

QP *, boru hattındaki herkesi alıp genişletecek ve bunları nesnelere bağlayacaktır (bu durumda, sütunların listesi). Daha sonra sorgunun doğası gereği gereksiz sütunları kaldıracaktır.

Dolayısıyla, bunun EXISTSgibi basit bir alt sorgu için :

SELECT col1 FROM MyTable WHERE EXISTS (SELECT * FROM Table2 WHERE MyTable.col1=Table2.col2)*Bazı potansiyel büyük sütun listesine genişletilecek ve daha sonra belirlenecek Bunun semantik EXISTS böylece temelde hepsi kaldırılabilir, bu sütunların herhangi gerektirmez.

"SELECT 1 ", sorgu derlemesi sırasında o tablo için gereksiz meta verileri incelemek zorunda kalmayacaktır.

Ancak, çalışma zamanında sorgunun iki biçimi aynı olacak ve aynı çalışma zamanlarına sahip olacaktır.

Bu sorguyu çeşitli sayıda sütun içeren boş bir tabloda ifade etmenin dört olası yolunu test ettim. SELECT 1vs SELECT *vs SELECT Primary_Keyvs SELECT Other_Not_Null_Column.

OPTION (RECOMPILE)Saniyedeki ortalama yürütme sayısını kullanarak sorguları bir döngüde çalıştırdım ve ölçtüm. Aşağıdaki sonuçlar

görüntü açıklamasını buraya girin

+-------------+----------+---------+---------+--------------+
| Num of Cols |    *     |    1    |   PK    | Not Null col |
+-------------+----------+---------+---------+--------------+
| 2           | 2043.5   | 2043.25 | 2073.5  | 2067.5       |
| 4           | 2038.75  | 2041.25 | 2067.5  | 2067.5       |
| 8           | 2015.75  | 2017    | 2059.75 | 2059         |
| 16          | 2005.75  | 2005.25 | 2025.25 | 2035.75      |
| 32          | 1963.25  | 1967.25 | 2001.25 | 1992.75      |
| 64          | 1903     | 1904    | 1936.25 | 1939.75      |
| 128         | 1778.75  | 1779.75 | 1799    | 1806.75      |
| 256         | 1530.75  | 1526.5  | 1542.75 | 1541.25      |
| 512         | 1195     | 1189.75 | 1203.75 | 1198.5       |
| 1024        | 694.75   | 697     | 699     | 699.25       |
+-------------+----------+---------+---------+--------------+
| Total       | 17169.25 | 17171   | 17408   | 17408        |
+-------------+----------+---------+---------+--------------+

As hiçbir tutarlı arasındaki kazanır görülebilir SELECT 1ve SELECT *ve bu ikisi arasındaki fark göz ardı edilebilir yaklaşır. SELECT Not Null colVe SELECT PKbiraz daha hızlı olsa görünüyor.

Tablodaki sütun sayısı arttıkça, sorguların dördünün de performansı düşer.

Tablo boş olduğundan, bu ilişki yalnızca sütun meta verilerinin miktarıyla açıklanabilir görünmektedir. Zira, COUNT(1)bunun COUNT(*)sürecin bir noktasında aşağıdan yeniden yazıldığını görmek kolaydır .

SET SHOWPLAN_TEXT ON;

GO

SELECT COUNT(1)
FROM master..spt_values

Aşağıdaki planı veren

  |--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1004],0)))
       |--Stream Aggregate(DEFINE:([Expr1004]=Count(*)))
            |--Index Scan(OBJECT:([master].[dbo].[spt_values].[ix2_spt_values_nu_nc]))

Aşağıdakileri yürütürken SQL Server sürecine bir hata ayıklayıcı ekleme ve rastgele kırma

DECLARE @V int 

WHILE (1=1)
    SELECT @V=1 WHERE EXISTS (SELECT 1 FROM ##T) OPTION(RECOMPILE)

Tablonun çoğu zaman 1.024 sütuna sahip olduğu durumlarda, çağrı yığınının aşağıdaki gibi göründüğünü buldum, bu da gerçekten SELECT 1kullanıldığında bile zaman yükleme sütunu meta verilerinin büyük bir bölümünü harcadığını gösterir ( tablonun 1 sütunu var rastgele kırma 10 denemede çağrı yığınının bu bitine ulaşmadı)

sqlservr.exe!CMEDAccess::GetProxyBaseIntnl()  - 0x1e2c79 bytes  
sqlservr.exe!CMEDProxyRelation::GetColumn()  + 0x57 bytes   
sqlservr.exe!CAlgTableMetadata::LoadColumns()  + 0x256 bytes    
sqlservr.exe!CAlgTableMetadata::Bind()  + 0x15c bytes   
sqlservr.exe!CRelOp_Get::BindTree()  + 0x98 bytes   
sqlservr.exe!COptExpr::BindTree()  + 0x58 bytes 
sqlservr.exe!CRelOp_FromList::BindTree()  + 0x5c bytes  
sqlservr.exe!COptExpr::BindTree()  + 0x58 bytes 
sqlservr.exe!CRelOp_QuerySpec::BindTree()  + 0xbe bytes 
sqlservr.exe!COptExpr::BindTree()  + 0x58 bytes 
sqlservr.exe!CScaOp_Exists::BindScalarTree()  + 0x72 bytes  
... Lines omitted ...
msvcr80.dll!_threadstartex(void * ptd=0x0031d888)  Line 326 + 0x5 bytes C
kernel32.dll!_BaseThreadStart@8()  + 0x37 bytes 

Bu manuel profil oluşturma girişimi, iki durum için derleme süresini tüketen çok farklı bir işlev seçimi gösteren VS 2012 kod profilcisi tarafından desteklenmektedir ( İlk 15 İşlev 1024 sütun ve İlk 15 İşlev 1 sütun ).

Hem ve SELECT 1hem de SELECT *sürümler sütun izinlerini kontrol eder ve kullanıcıya tablodaki tüm sütunlara erişim izni verilmezse başarısız olur.

Yığın üzerindeki bir sohbetten aldığım bir örnek

CREATE USER blat WITHOUT LOGIN;
GO
CREATE TABLE dbo.T
(
X INT PRIMARY KEY,
Y INT,
Z CHAR(8000)
)
GO

GRANT SELECT ON dbo.T TO blat;
DENY SELECT ON dbo.T(Z) TO blat;
GO
EXECUTE AS USER = 'blat';
GO

SELECT 1
WHERE  EXISTS (SELECT 1
               FROM   T); 
/*  ↑↑↑↑ 
Fails unexpectedly with 

The SELECT permission was denied on the column 'Z' of the 
           object 'T', database 'tempdb', schema 'dbo'.*/

GO
REVERT;
DROP USER blat
DROP TABLE T

Bu nedenle, kullanım sırasındaki küçük belirgin farkın SELECT some_not_null_col yalnızca o belirli sütundaki izinleri kontrol etmesidir (yine de herkes için meta verileri yükler). Ancak, temel tablodaki sütun sayısı arttıkça bir şey küçülürse, iki yaklaşım arasındaki yüzde farkı gerçeklere uymuyor gibi görünmektedir.

Her halükarda acele etmeyeceğim ve tüm sorgularımı bu forma değiştirmeyeceğim çünkü fark çok küçük ve yalnızca sorgu derleme sırasında belirgindir. OPTION (RECOMPILE)Sonraki yürütmelerin önbelleğe alınmış bir planı kullanabilmesi için kaldırılması aşağıdakileri verdi.

görüntü açıklamasını buraya girin

+-------------+-----------+------------+-----------+--------------+
| Num of Cols |     *     |     1      |    PK     | Not Null col |
+-------------+-----------+------------+-----------+--------------+
| 2           | 144933.25 | 145292     | 146029.25 | 143973.5     |
| 4           | 146084    | 146633.5   | 146018.75 | 146581.25    |
| 8           | 143145.25 | 144393.25  | 145723.5  | 144790.25    |
| 16          | 145191.75 | 145174     | 144755.5  | 146666.75    |
| 32          | 144624    | 145483.75  | 143531    | 145366.25    |
| 64          | 145459.25 | 146175.75  | 147174.25 | 146622.5     |
| 128         | 145625.75 | 143823.25  | 144132    | 144739.25    |
| 256         | 145380.75 | 147224     | 146203.25 | 147078.75    |
| 512         | 146045    | 145609.25  | 145149.25 | 144335.5     |
| 1024        | 148280    | 148076     | 145593.25 | 146534.75    |
+-------------+-----------+------------+-----------+--------------+
| Total       | 1454769   | 1457884.75 | 1454310   | 1456688.75   |
+-------------+-----------+------------+-----------+--------------+

Kullandığım test komut dosyası burada bulunabilir


3
+1 Bu yanıt, gerçek verileri elde etme çabası için daha fazla oyu hak ediyor.
Jon

1
Bu istatistiklerin hangi SQL Server sürümünde oluşturulduğu hakkında bir fikriniz var mı?
Martin Brown

3
@MartinBrown - IIRC aslen 2008 olsa da, en son düzenleme için 2012'de testleri son zamanlarda yeniden yaptım ve aynısını buldum.
Martin Smith

8

Bilmenin en iyi yolu, her iki sürümün performansını test etmek ve her iki sürüm için yürütme planına göz atmaktır. Çok sayıda sütun içeren bir tablo seçin.


2
+1. Bunun neden reddedildiğine dair hiçbir fikrim yok. Her zaman bir adama balık tutmaktansa balık tutmayı öğretmenin daha iyi olduğunu düşünmüşümdür. İnsanlar bir şeyi nasıl öğrenecek?
Ogre Mezmuru33

5

SQL Server'da hiçbir fark yoktur ve SQL Server'da hiç sorun olmamıştır. Optimize edici, aynı olduklarını bilir. Uygulama planlarına bakarsanız aynı olduklarını göreceksiniz.


1

Şahsen ben aynı sorgu planına göre optimizasyon yapmadıklarına inanmak çok ama çok zor. Ancak özel durumunuzu bilmenin tek yolu onu test etmektir. Eğer yaparsanız, lütfen bize bildirin!


-1

Gerçek bir fark yok, ancak çok küçük bir performans artışı olabilir. Genel bir kural olarak, ihtiyacınız olandan daha fazla veri istememelisiniz.

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.