ІСНУЄТЬСЯ (ВИБІР 1…) проти ІСНУЮЧИХ (ВИБОР * *) Одне чи інше?


38

Щоразу, коли мені потрібно перевірити наявність якогось рядка в таблиці, я прагну завжди писати умову на зразок:

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
       )

Деякі інші люди пишуть це так:

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
       )

Коли умова NOT EXISTSзамість EXISTS: У деяких випадках я можу написати це з LEFT JOINдодатковою умовою (іноді її називають антиприєднанням ):

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

Я намагаюся уникати цього, тому що я думаю, що сенс менш зрозумілий, особливо коли те, що є вашим primary_key, не настільки очевидно, або коли ваш основний ключ або ваша умова приєднання є багатоколонок (і ви можете легко забути один із стовпців). Однак іноді ви підтримуєте код, написаний кимось іншим ... і він просто є.

  1. Чи є якась різниця (крім стилю) використовувати SELECT 1замість SELECT *?
    Чи є кутовий випадок, коли він веде себе не так?

  2. Хоча те, що я написав, є (AFAIK) стандартним SQL: Чи існує така різниця для різних баз даних / старих версій?

  3. Чи є якась перевага щодо чіткості написання антиз'єднання?
    Чи ставляться до цього сучасні планувальники / оптимізатори по-різному від NOT EXISTSпункту?


5
Зауважте, що підтримка PostgreSQL вибирає без стовпців, тому ви можете просто писати EXISTS (SELECT FROM ...).
праворуч

1
Я задав майже те саме питання щодо SO пару років тому: stackoverflow.com/questions/7710153/…
Ервін Брандстеттер

Відповіді:


45

Ні, немає ніякої різниці в ефективності між (NOT) EXISTS (SELECT 1 ...)і (NOT) EXISTS (SELECT * ...)у всіх основних СУБД. Я часто бачив, (NOT) EXISTS (SELECT NULL ...)як його також використовують.

У деяких ви навіть можете написати (NOT) EXISTS (SELECT 1/0 ...)і результат однаковий - без будь-якої (поділ на нуль) помилки, що доводить, що вираз там навіть не оцінюється.


Щодо LEFT JOIN / IS NULLметоду антиз'єднання, виправлення: це еквівалентно NOT EXISTS (SELECT ...).

У цьому випадку NOT EXISTSvsLEFT JOIN / IS NULL, ви можете отримати різні плани виконання. Наприклад, у MySQL та здебільшого у старих версіях (до 5.7) плани були б досить схожі, але не тотожні. Наскільки я знаю, оптимізатори інших СУБД (SQL Server, Oracle, Postgres, DB2) більш-менш здатні переписати ці 2 методи та розглянути однакові плани для обох. Тим не менш, такої гарантії немає, і при оптимізації добре перевіряти плани з різних рівноцінних переписувань, оскільки можуть бути випадки, які кожен оптимізатор не переписує (наприклад, складні запити, з багатьма об'єднаннями та / або отриманими таблицями / підзапити в підзапиті, де на умови з декількох таблиць, складених стовпців, що використовуються в умовах приєднання) або на вибір та плани оптимізатора по-різному впливають наявні індекси, налаштування тощо.

Також зауважте, що USINGйого не можна використовувати у всіх СУБД (наприклад, SQL Server). Більш поширені JOIN ... ONтвори скрізь.
І стовпці повинні бути встановлені з назвою таблиці / псевдонімом у, SELECTщоб уникнути помилок / неоднозначностей, коли ми приєднуємось.
Я також вважаю за краще поставити об'єднаний стовпець у IS NULLчек (хоча PK або будь-який ненульовий стовпець буде добре, це може бути корисно для ефективності, коли план LEFT JOINвикористання некластеризованого індексу):

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 ;

Існує також третій метод антиз'єднання, використовуючи, NOT INале це має різну семантику (і результати!), Якщо стовпець внутрішньої таблиці є нульовим. Його можна використовувати, хоча виключаючи рядки з NULL, роблячи запит еквівалентним попереднім 2 версіям:

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
       ) ;

Зазвичай це дає подібні плани в більшості СУБД.


1
До самих останніх версій MySQL [NOT] IN (SELECT ...), хоча і еквівалентна, виконувалася дуже слабо. Уникайте цього!
Рік Джеймс

4
Це не відповідає дійсності для PostgreSQL . SELECT *це, звичайно, більше роботи. Я б для простоти радив використовуватиSELECT 1
Еван Керролл

11

Існує одна категорія випадків , коли SELECT 1і SELECT *не є взаємозамінними - більш конкретно, один завжди буде прийнятий в тих випадках , в той час як інший в більшості випадків не буде.

Я говорю про випадки, коли потрібно перевірити наявність рядків згрупованого набору. Якщо таблиця Tмістить стовпці C1і C2ви перевіряєте наявність груп рядків, які відповідають певній умові, ви можете використовувати SELECT 1так:

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

але ви не можете використовувати SELECT *так само

Це просто синтаксичний аспект. Якщо обидва варіанти приймаються синтаксично, ви, швидше за все, не матиме різниці в показниках ефективності чи результатах, що були повернуті, як було пояснено в іншій відповіді .

Додаткові примітки після коментарів

Здається, не так багато продуктів бази даних насправді підтримують цю відмінність. Такі продукти як SQL Server, Oracle, MySQL та SQLite із задоволенням прийматимуть SELECT *вищезазначений запит без будь-яких помилок, що, ймовірно, означає, що вони по-особливому ставляться до EXISTS SELECT.

PostgreSQL - це одна RDBMS, де SELECT *може вийти з ладу, але в деяких випадках все ще може працювати. Зокрема, якщо ви згрупуєте PK, SELECT *буде добре працювати, інакше це не вдасться із повідомленням:

ПОМИЛКА: стовпець "T.C2" повинен з'являтися в пункті GROUP BY або використовуватись у сукупній функції


1
Хороші моменти, хоча я не зовсім так хвилював мене. У цьому показана концептуальна різниця. Тому що, коли ти GROUP BY, поняття " *безглуздо" (або, принаймні, не так зрозуміло).
joanolo

5

Напевно, цікавий спосіб переписати EXISTSстаттю, що призводить до отримання більш чистого та, можливо, менш оманливого запиту, принаймні на SQL Server:

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

Версія анти-напівз'єднання виглядатиме так:

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

Обидва типи оптимізовані під той самий план, що WHERE EXISTSі WHERE NOT EXISTS, але, намір безпомилковий, і у вас немає "дивного" 1або *.

Цікаво, що пов'язані з NOT IN (...)проблемою нульової перевірки є проблематичними <> ALL (...), тоді як проблеми NOT EXISTS (...)не страждають. Розглянемо наступні дві таблиці з нульовим стовпцем:

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
);

Ми додамо деякі дані до обох, деякі рядки збігаються, а деякі не відповідають:

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;
+ -------- + ----------- +
| ID | 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;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
| 4 | 4 |
+ -------- + ----------- +

NOT IN (...)запит:

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

Має такий план:

введіть тут опис зображення

Запит не повертає рядків, оскільки значення NULL унеможливлюють підтвердження рівності.

Цей запит із <> ALL (...)показаним однаковим планом і не повертає рядків:

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

введіть тут опис зображення

Варіант, що використовує NOT EXISTS (...), показує дещо іншу форму плану та повертає рядки:

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

План:

введіть тут опис зображення

Результати запиту:

+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +

Це робить використання таких <> ALL (...)же схильних до проблемних результатів, як і NOT IN (...).


3
Треба сказати, що мені це не *дивно: я читаю EXISTS (SELECT * FROM t WHERE ...) AS there is a _row_ in table _t_ that.... У всякому разі, мені подобається мати альтернативи, і ваша чітко читається. Один сумнів / застереження: як він поводитиметься, якщо bє незмінним? [У мене були погані переживання та кілька коротких ночей, коли я намагався з’ясувати помилку, спричинену a x IN (SELECT something_nullable FROM a_table)]
joanolo

EXISTS повідомляє, чи має таблиця рядок та повертає true чи false. ІСНУЄТЬСЯ (SELECT x FROM (values ​​(null)) true. IN is = ANY & NOT IN is <> ALL. Ці 4 приймають рядок RHS з NULLs, щоб можливо збігатися. (X) = ANY (значення (null)) & (x) <> ALL (значення (null)) є невідомими / null, але EXISTS (значень (null)) є істинним. (IN & = БУДЬ-які є ті самі "проблеми з нульовою перевіркою, пов'язані з NOT IN (...) [& ] <> ВСЕ (...) ". БУДЬ-ЯКОГО І ВСЕ ітерація АБО & І. Але є лише" проблеми ", якщо ви не організуєте семантику за призначенням.) Не радить використовувати їх для ВИСТАВКИ. Вони вводять в оману. , не "менш оманливі".
philipxy

@philliprxy - Якщо я помиляюся, у мене немає проблем зізнатися. Не соромтесь додати свою відповідь, якщо вам це подобається.
Макс Вернон

4

"Доказ" того, що вони однакові (у MySQL), - це зробити

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

потім повторіть с SELECT 1. В обох випадках «розширений» вихід показує, що він був перетворений в SELECT 1.

Аналогічно COUNT(*)перетворюється на COUNT(0).

Ще одне, що слід зазначити: в останніх версіях було вдосконалено оптимізацію. Можливо, варто порівняти EXISTSпроти антиз'єднання. Ваша версія може зробити кращу роботу з однією проти іншої.


4

У деяких базах даних ця оптимізація ще не працює. Як, наприклад, у PostgreSQL станом на версію 9.6, це не вдасться.

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
);

І це вдасться.

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
);

Це не вдається, тому що наступне не вдається, але це все ще означає, що є різниця.

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

Ви можете знайти більш детальну інформацію про цю конкретну химерність та порушення специфікації у моїй відповіді на запитання, чи вимагає специфікація SQL GROUP BY в EXISTS ()


Рідкісний кутовий випадок, трохи дивний, можливо, але знову ж таки - доказ того, що вам потрібно робити багато компромісів при розробці бази даних ...
joanolo

-1

Я завжди користувався select top 1 'x' (SQL Server)

Теоретично, select top 1 'x' було б більш ефективноselect * , оскільки перший був би завершеним після вибору константи про наявність кваліфікованого ряду, тоді як останній вибирав би все.

ЗАРАЗ, хоча це дуже рано може бути актуальним, оптимізація зробила різницю неактуальною, ймовірно, у всіх основних RDBS.


Має сенс. Це може бути (або, можливо, був) одним із небагатьох випадків, коли top nбез order byних є гарна ідея.
joanolo

3
"Теоретично, ...." Ні, теоретично select top 1 'x'не повинно бути ефективнішим, ніж select *у Existвиразі. Практично це може бути ефективнішим, якщо оптимізатор працює неоптимально, але теоретично обидва вирази рівнозначні.
чудо173

-4

IF EXISTS(SELECT TOP(1) 1 FROMє кращою звичкою в довгостроковій перспективі та на різних платформах просто тому, що вам навіть не потрібно починати турбуватися про те, наскільки хороша чи погана ваша поточна платформа / версія; і SQL рухається в TOP nбік параметруемого TOP(n). Це має бути навик одного разу.


3
Що ви маєте на увазі під "на різних платформах" ? TOPнавіть не є дійсним SQL.
ypercubeᵀᴹ

"SQL рухається .." - це явно неправильно. У TOP (n)"SQL" немає - стандартної мови запитів. На T-SQL є діалект, який використовує Microsoft SQL Server.
a_horse_with_no_name

Тег на оригінальне запитання - "SQL Server". Але це добре, щоб зняти участь і посперечатися з тим, що я сказав - це мета цього веб-сайту в тому, щоб увімкнути просту трансляцію. Хто я, щоб дощу на вашому параді з нудною увагою до деталей?
ajeh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.