Чому цей запит, відсутній у пункті 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'

Відповіді:


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. Дивіться цю публікацію для отримання додаткової інформації .


Я задав запитання щодо Stack Overflow, на яке відповідь по суті такий же, як і цей (хоча ваш більш ретельний). Чому посилання на стовпець (як лівий операнд), який не є частиною таблиці, не запитується, не є помилкою оператора EXISTS?
Марк.2377,

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 оцінюються для кожного ряду.


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 . У підзапиті / похідній таблиці.
Аарон Бертран
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.