Почему в этом запросе отсутствует предложение FROM, а не ошибка?

9

Итак, у нас есть запрос с подзапросом, в котором есть опечатка. В нем отсутствует предложение FROM. Но когда вы запускаете его, это не ошибка! Почему!?


SELECT

    1
   ,r.id
   ,'0D4133BE-C1B5-4141-AFAD-B171A2CCCE56'
   ,GETDATE()
   ,1
   ,'Y'
   ,'N'
   ,oldItem.can_view
   ,oldItem.can_update

FROM Role r

JOIN RoleObject oldReport
    ON r.customer_id = oldReport.customer_id

JOIN RoleItem oldItem
    ON oldReport.id = oldItem.role_object_id
        AND r.id = oldItem.role_id

WHERE r.id NOT IN (SELECT
        role_id
    WHERE role_object_id = '0D4133BE-C1B5-4141-AFAD-B171A2CCCE56')

AND oldReport.id = '169BA22F-1614-4EBA-AF45-18E333C54C6C'
Wjdavis5
источник

Ответы:

21

Это утверждение является законным (другими словами, нет FROMнеобходимости):

SELECT x = 1;
SELECT x = 1 WHERE 1 = 1; -- also try WHERE 1 = 0;

Хитрость заключается в том, что вы вводите имя столбца, которое явно не может существовать. Таким образом, они терпят неудачу:

SELECT name WHERE 1 = 1;

SELECT x = 1 WHERE id > 0;

Сообщение 207, уровень 16, состояние 1
Неверное имя столбца «имя».
Сообщение 207, уровень 16, состояние 1
Неверное имя столбца 'id'.

Но когда недопустимый столбец вводится во что-то вроде подзапроса, то, что делает SQL Server, когда не может найти этот столбец во внутренней области подзапроса, переходит во внешнюю область и делает подзапрос коррелированным с этой внешней областью. Это вернет все строки, например:

SELECT * FROM sys.columns WHERE name IN (SELECT name WHERE 1 = 1);

Потому что по сути это говорит:

SELECT * FROM sys.columns WHERE name IN (SELECT sys.columns.name WHERE 1 = 1); /*
              ^^^^^^^^^^^                       -----------
                   |                                 |
                   -----------------------------------    */

Вам даже не нужно WHEREпредложение в подзапросе:

SELECT * FROM sys.columns WHERE name IN (SELECT name);

Вы можете видеть, что он действительно смотрит на внешнюю таблицу с областями видимости, потому что это:

SELECT * FROM sys.columns WHERE name IN (SELECT name WHERE name > N'x');

Возвращает гораздо меньше строк (11 в моей системе).

Это подразумевает соблюдение стандарта о сфере охвата. Вы можете увидеть похожие вещи, когда у вас есть две таблицы #temp:

CREATE TABLE #foo(foo int);
CREATE TABLE #bar(bar int);

SELECT foo FROM #foo WHERE foo IN (SELECT foo FROM #bar);

Очевидно, это должно быть ошибкой, верно, так как нет fooв #bar? Нет. В результате SQL Server говорит: «О, я не нашел fooздесь, вы, должно быть, имели в виду другое».

Также, в общем, я бы избегал NOT IN. NOT EXISTSобладает потенциалом быть более эффективным в некоторых сценариях, но, что более важно, его поведение не меняется, когда возможно, что целевой столбец может быть NULL. Смотрите этот пост для получения дополнительной информации .

Аарон Бертран
источник
Я задал вопрос о переполнении стека, ответ на который, по сути, такой же, как этот (хотя у вас более полный). Почему ссылка на столбец (как левый операнд), который не является частью запрашиваемой таблицы, не является ошибкой в ​​операторе EXISTS?
23
2

Я воспроизвел это в 2016 году на упрощенном примере:

declare @t1 table (c1 int, c2 int, c3 int)
insert into @t1 values (1,2,3), (2,3,4), (3,4,5)

select * from @t1
where
    c1 not in 
    (select c2 where c3 = 3)

Похоже, что c2 и c3 оцениваются для каждой строки.

paulbarbin
источник
1

В SQL Server синтаксис SELECT не требует раздела FROM. Если вы опустите FROM, оператор select будет использовать «фиктивную» таблицу, которая имеет одну строку и не содержит столбцов. Так

select 'x' as c where ...

вернет одну строку, если выражение истинно, и не будет строк, если оно ложно.

Петр
источник
Но это не сработает, если вы просто скажете, select cи cне существует в каком-то внешнем объекте. Я согласен, что FROMэто не обязательно, но механика, которая используется здесь, когда вы явно указываете столбец, который существует во внешней области видимости, определенно отличается от фиктивной таблицы, и если вы не предоставляете константу для столбца, который не Если вы существуете, вы получаете ошибку во время выполнения, поэтому там нет и фиктивной таблицы. Фиктивная таблица может вступать в игру в других сценариях, но не тогда, когда ссылка находится в подзапросе / производной таблице.
Аарон Бертран
В вашем примере это коррелированный суб-выбор, role_id и role_object_id принадлежит к одной из таблиц во внешнем выборе.
Петр
Правильно, но высказывание SELECT 'x' AS c- это совершенно другой сценарий, чем у ОП, который только что сказал SELECT c. В подзапросе / производной таблице.
Аарон Бертран