СУЩЕСТВУЕТ (ВЫБРАТЬ 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. Хотя то, что я написал, является стандартным SQL (AFAIK): есть ли такая разница для разных баз данных / более старых версий?

  3. Есть ли какое-то преимущество в простоте написания противодействия?
    Современные планировщики / оптимизаторы рассматривают это иначе, чем NOT EXISTSпункт?

joanolo
источник
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 EXISTSпротивLEFT JOIN / IS NULLВы можете получить разные планы выполнения. Например, в MySQL и в основном в более старых версиях (до 5.7) планы были бы довольно похожими, но не идентичными. Оптимизаторы других СУБД (SQL Server, Oracle, Postgres, DB2) - насколько я знаю - более или менее способны переписать эти два метода и учитывать одинаковые планы для обоих. Тем не менее, нет такой гарантии, и при выполнении оптимизации полезно проверять планы из разных эквивалентных перезаписей, поскольку могут быть случаи, когда каждый оптимизатор не переписывает (например, сложные запросы со многими объединениями и / или производными таблицами / подзапросы внутри подзапроса, где условия из нескольких таблиц, составных столбцов, используемых в условиях соединения) или выбор и планы оптимизатора по-разному зависят от доступных индексов, настроек и т. д.

Также обратите внимание, что 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
       ) ;

Это также обычно дает аналогичные планы в большинстве СУБД.

ypercubeᵀᴹ
источник
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 - это одна СУБД, в которой SELECT *может произойти сбой, но в некоторых случаях она может работать. В частности, если вы группируете по PK, все SELECT *будет работать нормально, иначе произойдет сбой с сообщением:

ОШИБКА: столбец "T.C2" должен появляться в предложении GROUP BY или использоваться в статистической функции

Андрей М
источник
1
Хорошие моменты, хотя это не совсем тот случай, который меня беспокоил. Этот показывает концептуальную разницу. Потому что, когда вы GROUP BY, понятие *бессмысленно (или, по крайней мере, не так ясно).
Жоаноло
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 ...) КАК there is a _row_ in table _t_ that.... Во всяком случае, мне нравится иметь альтернативы, и ваш ясно читается. Одно сомнение / предостережение: как он будет себя вести, если bобнуляется? [У меня были плохие переживания и несколько коротких ночей, когда я пытался выяснить ошибку, вызванную a x IN (SELECT something_nullable FROM a_table)]
joanolo
EXISTS сообщает вам, есть ли в таблице строка, и возвращает true или false. EXISTS (SELECT x FROM (values ​​(null)) имеет значение true. IN is = ANY, а NOT IN is <> ALL. Эти 4 принимают строку RHS с NULL для возможного совпадения. (X) = ANY (values ​​(null)) & (x) <> ALL (значения (null)) неизвестны / нулевые, но EXISTS (values ​​(null)) имеет значение true. (IN & = ANY имеют одинаковые проблемы с проверкой нуля, связанные с NOT IN (...) [& ] <> ALL (...) ". ЛЮБОЙ & ВСЕ итерируют ИЛИ & И. Но есть только" проблемы ", если вы не организовали семантику, как предполагалось.) Не советуйте использовать их для EXISTS. Они вводят в заблуждение , не "менее вводит в заблуждение".
Филиппа
@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.

G DeMasters
источник
Имеет смысл. Это может быть (или могло бы быть) одним из очень немногих случаев, когда top nбез order byхорошей идеи.
Joanolo
3
«Теоретически…». Нет, теоретически select top 1 'x'не должно быть более эффективным, чем select *в Existвыражении. Практически это может быть более эффективно, если оптимизатор работает неоптимально, но теоретически оба выражения эквивалентны.
miracle173
-4

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

ajeh
источник
3
Что вы имеете в виду под "через платформы" ? TOPэто даже не допустимый SQL.
ypercubeᵀᴹ
«SQL движется ..» совершенно неправильно. Нет TOP (n)в «SQL» - стандартного языка запросов. Существует один на T-SQL, который является диалектом, который использует Microsoft SQL Server.
a_horse_with_no_name
Тег на исходный вопрос «SQL Server». Но это нормально, чтобы понизить и оспорить то, что я сказал - цель этого сайта - включить легкое понижение. Кто я такой, чтобы идти на твой парад со скучным вниманием к деталям?
ajeh