EXISTS (SELECT 1…) vs EXISTS (SELECT *…) Biri veya diğeri?


37

Ne zaman bir masada bir satırın olup olmadığını kontrol etmem gerektiğinde, her zaman şöyle bir koşul yazma eğilimindeyim:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT *  -- This is what I normally write
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Diğer bazı insanlar şöyle yazıyor:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT 1   --- This nice '1' is what I have seen other people use
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Durum NOT EXISTSyerine EXISTS: Ne zaman bazı durumlarda, ben LEFT JOINve ekstra bir durumla (bazen bir antijoin denir ) yazabilirim :

SELECT a, b, c
  FROM a_table
       LEFT JOIN another_table ON another_table.b = a_table.b
 WHERE another_table.primary_key IS NULL

Bundan kaçınmaya çalışıyorum, çünkü anlamın daha az açık olduğunu düşünüyorum, özellikle sizin açık olanın ne primary_keykadar belirgin olmadığı veya birincil anahtarınız veya birleştirme şartınız çok sütunlu olduğunda (ve sütunlardan birini kolayca unutabilirsiniz). Ancak, bazen başka biri tarafından yazılan kodu koruyorsunuz ... ve tam da orada.

  1. Kullanmak SELECT 1yerine ( stil dışında) herhangi bir fark var mı SELECT *?
    Aynı şekilde davranmadığı herhangi bir köşe kılıfı var mı?

  2. Yazdığım (AFAIK) standart SQL olmasına rağmen: Farklı veritabanları / eski sürümler için böyle bir fark var mı?

  3. Bir karşıtlık yazma açıklığının herhangi bir avantajı var mı?
    Çağdaş planlamacılar / optimize ediciler, onu NOT EXISTSmaddeden farklı şekilde ele alıyor mu?


5
PostgreSQL'in sütunsuz seçimleri desteklediğini unutmayın; böylece yazabilirsiniz EXISTS (SELECT FROM ...).
sağa doğru

1
Birkaç yıl önce
SO'da

Yanıtlar:


45

Hayır, tüm büyük DBMS'lerde (NOT) EXISTS (SELECT 1 ...)ve (NOT) EXISTS (SELECT * ...)arasında verimlilik açısından bir fark yoktur . Ben de sıklıkla (NOT) EXISTS (SELECT NULL ...)kullanıldığını gördüm .

Bazılarında bile yazabilirsiniz (NOT) EXISTS (SELECT 1/0 ...)ve sonuç aynıdır - (sıfıra bölme) hatası olmadan, oradaki ifadenin bile değerlendirilmediğini kanıtlar.


Hakkında LEFT JOIN / IS NULLantijoin yöntemle, bir düzeltme: Bu eşdeğerdir NOT EXISTS (SELECT ...).

Bu durumda, NOT EXISTSvsLEFT JOIN / IS NULL, farklı idam planları alabilirsiniz. Örneğin, MySQL'de ve çoğunlukla eski sürümlerde (5.7'den önce), planlar oldukça benzer fakat aynı olmazdı. Diğer DBMS’lerin (SQL Server, Oracle, Postgres, DB2) optimize edicileri - bildiğim kadarıyla - bu 2 yöntemi tekrar yazma ve her ikisi için de aynı planları göz önünde bulundurma yeteneğine sahiptir. Yine de, böyle bir garanti yoktur ve optimizasyon yaparken, planları her eşleştiricinin yeniden yazmadığı durumlar olabileceğinden farklı eşdeğer yeniden yazmalardan kontrol etmek iyidir (örneğin, birçok sorgulama ve / veya türetilmiş tablolarla birlikte karmaşık sorgular / Birden fazla tablodan koşulların, birleştirme koşullarında kullanılan bileşik sütunların) veya optimize edici seçeneklerin ve planların mevcut dizinlerden, ayarlardan vb. farklı şekilde etkilendiği alt sorgu içindeki alt sorgular.

Ayrıca USINGtüm DBMS’lerde kullanılamayacağını unutmayın (örneğin, SQL Server). Her JOIN ... ONyerde daha yaygın işler.
Katıldığımızda SELECThatalardan / belirsizliklerden kaçınmak için sütunlara, ad / takma isimler eklenmiş olmalıdır .
Ayrıca, birleştirilmiş sütunu IS NULLkontrolde bulundurmayı da tercih ederim (PK ya da nULL olmayan tüm sütunlar tamam olsa da, LEFT JOINkümelenmemiş bir dizin kullanımında verimlilik için yararlı olabilir ):

SELECT a_table.a, a_table.b, a_table.c
  FROM a_table
       LEFT JOIN another_table 
           ON another_table.b = a_table.b
 WHERE another_table.b IS NULL ;

Antijoins için kullanılan üçüncü bir yöntem de vardır, NOT INancak iç tablonun sütunu null ise, bunun farklı bir semantiği vardır (ve sonuçları!). Bununla birlikte NULL, satırları hariç tutarak , sorguyu önceki 2 versiyona eşdeğer hale getirerek kullanılabilir:

SELECT a, b, c
  FROM a_table
 WHERE a_table.b NOT IN 
       (SELECT another_table.b
          FROM another_table
         WHERE another_table.b IS NOT NULL
       ) ;

Bu aynı zamanda çoğu DBMS'de de benzer planlara sahiptir.


1
Çok yakın zamana kadar MySQL sürümleri, [NOT] IN (SELECT ...)eşdeğer olmasına rağmen, çok düşük performans gösterdi. Bunu önlemek!
Rick James,

3
Bu PostgreSQL için doğru değil . SELECT *kesinlikle daha fazla iş yapıyor. Sadelik iyiliği için tavsiye ediyorumSELECT 1
Evan Carroll

11

Değiştirilemez olan SELECT 1ve SELECT *değiştirilemeyen bir vaka kategorisi vardır - daha spesifik olarak, biri bu durumlarda her zaman kabul edilirken diğeri çoğunlukla kabul edilmez.

Gruplandırılmış bir kümenin satırlarının varlığını kontrol etmeniz gereken vakalardan bahsediyorum . Tabloda Tsütunlar varsa C1ve C2belirli bir koşulla eşleşen satır gruplarının varlığını kontrol ediyorsanız, bunun SELECT 1gibi kullanabilirsiniz :

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)

ama SELECT *aynı şekilde kullanamazsınız .

Bu sadece sözdizimsel bir yönüdür. Her iki seçeneğin de sözdizimsel olarak kabul edilmesi durumunda, diğer cevapta açıklandığı gibi, büyük olasılıkla performans veya geri gönderilen sonuçlar açısından hiçbir farkınız olmaz .

Yorumların ardından ek notlar

Birçok veritabanı ürününün aslında bu ayrımı desteklemediği görülüyor. SQL Server, Oracle, MySQL ve SQLite gibi ürünler SELECT *, yukarıdaki sorguda hatasız olarak mutlu bir şekilde kabul eder , bu muhtemelen bir EXISTS'e SELECTözel bir şekilde davranmaları anlamına gelir .

PostgreSQL SELECT *başarısız olabileceği bir RDBMS'dir , ancak yine de bazı durumlarda çalışabilir. Özellikle, PK ile gruplandırıyorsanız, SELECT *iyi çalışacak, aksi takdirde mesajla başarısız olacaktır:

ERROR: "T.C2" sütunu GROUP BY deyiminde görünmeli veya toplama işlevinde kullanılmalıdır


1
İyi noktalar, bu tam olarak endişelendiğim durum olmasa da. Bu, kavramsal bir fark gösterir . Çünkü siz ne zaman GROUP BYkavramı *anlamsızdır (veya en azından net değil).
joanolo

5

EXISTSEn azından SQL Server'da daha temiz ve belki de daha az yanıltıcı bir sorguyla sonuçlanan maddeyi yeniden yazmanın tartışmalı olarak ilginç bir yolu :

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );

Bunun anti-semi-join sürümü şöyle görünür:

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );

Her ikisi de, tipik olarak WHERE EXISTSveya ile aynı plana göre optimize edilmiştir WHERE NOT EXISTS, ancak niyet kesindir ve "tuhaf" 1veya hiçbiriniz yoktur *.

İlginçtir, ilişkili boş kontrol problemleri NOT IN (...)problemlidir <> ALL (...), oysa NOT EXISTS (...)ki problem bu değildir. Boşaltılabilir bir sütuna sahip aşağıdaki iki tabloyu göz önünde bulundurun:

IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
    DROP TABLE #t;
END;
CREATE TABLE #t 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
    DROP TABLE #s;
END;
CREATE TABLE #s 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

Her ikisine de, bazı satırlarla eşleşen ve bazılarında uymayan bazı veriler ekleyeceğiz:

INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);

SELECT *
FROM #t;
+ -------- + ----------- +
| Kimlik | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);

SELECT *
FROM #s;
+ -------- + ----------- +
| Kimlik | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
| 4 | 4 |
+ -------- + ----------- +

NOT IN (...)sorgu:

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );

Aşağıdaki plana sahiptir:

görüntü tanımını buraya girin

NULL değerleri eşitliği onaylamak imkansız hale getirdiğinden, sorgu hiçbir satır döndürmez.

Bu sorgu, <> ALL (...)aynı planı gösterir ve satır döndürmez:

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );

görüntü tanımını buraya girin

Kullanılan değişken NOT EXISTS (...), biraz farklı bir plan şekli gösterir ve satırları döndürür:

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );

Plan:

görüntü tanımını buraya girin

Bu sorgunun sonuçları:

+ -------- + ----------- +
| Kimlik | SomeValue |
+ -------- + ----------- +
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +

Bu da <> ALL (...)sorunlu sonuçlara eğilimlidir NOT IN (...).


3
*Garip olduğumu bulamadığımı söylemeliyim : EXISTS (SELECT * FROM t WHERE ...) AS okudum there is a _row_ in table _t_ that.... Her neyse, alternatifleri severim ve seninki açıkça okunabilir. Bir şüphe / ihmal: bnull ise nasıl davranır ? [Bir tecrübenin yaşadığı kötü geceler yaşadım ve kısa bir gecenin neden olduğu bir sıkıntıyı ortaya çıkarmaya çalıştım x IN (SELECT something_nullable FROM a_table)]
joanolo

EXISTS, bir tablonun satır olup olmadığını doğru veya yanlış döndürdüğünü gösterir. MEVCUT (SEÇ (değerler (null)) DAN x doğrudur. IN olduğunu = HERHANGİ & DEĞİL İÇİNDE olduğunu <> TÜM. Bunlar 4) muhtemelen uyuyor. (X boş değerlere sahip bir RHS satır almak = HERHANGİ (değerler (boş)) & (x) <> ALL (değerler (boş)) bilinmiyor / boş, ancak EXISTS (değerler (boş)) doğru. (IN & = ANY, NOT IN (...) ile ilişkili "null kontrol sorunlarına sahipler [& ] <> ALL (...) ". HERHANGİ VE TÜM yinelemeli VEYA VE VE. Ancak, semantiği tasarlandığı gibi organize etmiyorsanız, sadece" sorunlar "vardır.) Bunları EXISTS için kullanmanızı önermeyin. Yanıltıcı , "daha az yanıltıcı" değil
philipxy

@ philliprxy - Eğer yanlışsam, kabul etmekte hiçbir sorunum yok. İsterseniz kendi cevabınızı eklemek için çekinmeyin.
Max Vernon,

4

Aynı olduklarının "ispatı" (MySQL'de) yapmaktır.

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;

sonra tekrarlayın SELECT 1. Her iki durumda da 'genişletilmiş' çıktı, bunun dönüştürüldüğünü gösterir SELECT 1.

Benzer şekilde, COUNT(*)dönüşür COUNT(0).

Unutulmaması gereken başka bir şey: En son sürümlerinde optimizasyon iyileştirmeleri yapılmıştır. EXISTSAnti-birleşme vs karşılaştırmaya değer olabilir . Sürümünüz biri ile diğeriyle daha iyi bir iş yapabilir.


4

Bazı veritabanlarında bu optimizasyon henüz çalışmıyor. Örneğin PostgreSQL'deki gibi 9.6 sürümünden itibaren bu başarısız olacaktır.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT *
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Ve bu başarılı olacak.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT 1  -- This changed from the first query
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Başarısız oluyor çünkü aşağıdakiler başarısız oluyor, ancak bu hala bir fark olduğu anlamına geliyor.

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;

Soruya cevabımdaki bu tuhaflık ve şartnamenin ihlali hakkında daha fazla bilgi bulabilirsiniz , SQL Spec, EXISTS'de bir GRUP BY gerektiriyor mu ()


Nadir bir köşe davası, belki biraz garip , ama yine de, bir veritabanı tasarlarken çok fazla ödün vermeniz gerektiğini kanıtladı ...
joanolo

-1

Her zaman kullandım select top 1 'x'(SQL Server)

Teorik olarak, birincilerin bir eleme sırasının varlığına ilişkin bir sabit seçildikten sonra tamamlanacağından, ikincisi her şeyi seçeceği select top 1 'x'için daha verimli select *olacaktır.

Bununla birlikte, çok erken bir tarihte alakalı olsa da, optimizasyon farkı büyük olasılıkla tüm ana RDBS'lerde önemsiz hale getirdi.


Mantıklı. top nOlmadan order byiyi bir fikir olmadığı çok az vakadan biri olabilir (ya da olabilirdi) .
joanolo,

3
"Teorik olarak, ...." Hayır, teorik select top 1 'x'olarak daha verimli olmamalıselect * olarak bir Existifadeden . Optimize edicinin en düşük seviyede çalışıyor olması pratikte daha etkili olabilir ancak teorik olarak her iki ifade de aynıdır.
mucize173

-4

IF EXISTS(SELECT TOP(1) 1 FROMuzun vadeli ve platformlar arasında daha iyi bir alışkanlıktır, çünkü mevcut platformunuzun / sürümünüzün ne kadar iyi veya kötü olduğu konusunda endişelenmenize gerek yoktur; ve SQL TOP nparametrelendirilebilir olana doğru ilerliyor TOP(n). Bu bir kez öğrenilebilecek bir beceri olmalıdır.


3
"Platformlar arası" ile ne demek istiyorsunuz ? TOPgeçerli bir SQL bile değil.
ypercubeᵀᴹ

"SQL hareket ediyor .." düz yanlış. TOP (n)Standart sorgu dili - "SQL" de yok . Microsoft SQL Server'ın kullandığı lehçe olan T-SQL'de bir tane var.
a_horse_with_no_name

Orijinal sorudaki etiket "SQL Server" dır. Ancak söylediklerimi reddetmek ve itiraz etmek sorun değil - bu sitenin kolay reddedilmesini sağlamak için amaç budur. Ayrıntılara sıkıcı dikkat ederek geçit töreninize kimi yağdıracağım?
ajeh
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.