Dlaczego w tym zapytaniu, które nie zawiera klauzuli FROM, nie występuje błąd?


9

Mamy więc zapytanie z podzapytaniem, które zawiera literówkę. Brakuje klauzuli FROM. Ale kiedy go uruchomisz, nie wystąpi błąd! Dlaczego!?


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'

Odpowiedzi:


21

To oświadczenie jest legalne (innymi słowy, nie FROMjest wymagane):

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

Sztuką jest wprowadzenie nazwy kolumny, która najwyraźniej nie może istnieć. Te zawodzą:

SELECT name WHERE 1 = 1;

SELECT x = 1 WHERE id > 0;

Msg 207, poziom 16, stan 1
Niepoprawna nazwa kolumny „nazwa”.
Msg 207, poziom 16, stan 1
Niepoprawna nazwa kolumny „id”.

Ale gdy niepoprawna kolumna zostanie wprowadzona do czegoś w rodzaju podkwerendy, to co SQL Server robi, gdy nie może znaleźć tej kolumny w wewnętrznym zakresie podkwerendy, przechodzi do zakresu zewnętrznego i powoduje, że podkwerenda jest skorelowana z tym zakresem zewnętrznym. Zwróci to wszystkie wiersze, na przykład:

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

Ponieważ zasadniczo mówi:

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

Nie potrzebujesz nawet WHEREklauzuli w podzapytaniu:

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

Widać, że naprawdę patrzy na zewnętrzny stół z lunetą, ponieważ to:

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

Zwraca znacznie mniej wierszy (11 w moim systemie).

Wymaga to przestrzegania standardu określania zakresu. Możesz zobaczyć podobne rzeczy, gdy masz dwie tabele #temp:

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

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

Oczywiście, należy ten błąd, w prawo, ponieważ nie jest foow #bar? Nie. To, co się dzieje, to to, że SQL Server mówi: „och, nie znalazłem footutaj, musiałeś mieć na myśli ten drugi”.

Poza tym ogólnie bym unikał NOT IN. NOT EXISTSma potencjał, aby być bardziej wydajnym w niektórych scenariuszach, ale co ważniejsze, jego zachowanie nie zmienia się, gdy możliwe jest, że kolumna docelowa mogłaby być NULL. Zobacz ten post, aby uzyskać więcej informacji .


Zadałem pytanie na temat Przepełnienia stosu, na które odpowiedź jest w zasadzie taka sama (chociaż twoja jest dokładniejsza). Dlaczego odwoływanie się do kolumny (jako operand po lewej stronie), która nie jest częścią zapytania, nie jest błędem w operatorze EXISTS?
Marc.2377,

2

Powtórzyłem to w 2016 roku na uproszczonym przykładzie:

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)

Wygląda na to, że c2 i c3 są oceniane dla każdego wiersza.


1

W SQL Server składnia SELECT nie wymaga sekcji OD. Jeśli pominiesz FROM, instrukcja select użyje „sztucznej” tabeli, która ma jeden wiersz i nie zawiera kolumn. Więc

select 'x' as c where ...

zwróci jeden wiersz, jeśli wyrażenie jest prawdziwe, a żadnych wierszy, gdy jest fałszem.


Ale to nie działa, jeśli tylko powiesz select ci cnie istnieje w jakimś zewnętrznym obiekcie. Zgadzam się, że FROMnie jest to wymagane, ale mechanika w grze tutaj, gdy jawnie nazwiesz kolumnę, która istnieje w zasięgu zewnętrznym, zdecydowanie różni się od tabeli zastępczej i jeśli nie podasz stałej dla kolumny, która nie istnieje, pojawia się błąd czasu wykonania, więc nie ma tam również tabeli zastępczej. Tabela manekina może wejść w grę w innych scenariuszach, ale nie wtedy, gdy odwołanie znajduje się w tabeli podzapytania / pochodnej.
Aaron Bertrand

W twoim przykładzie jest to skorelowany podselekcja, rola_id i rola_obiektu należy do jednej z tabel w zewnętrznej selekcji.
Piotr

Zgadza się, ale powiedzenie SELECT 'x' AS cto zupełnie inny scenariusz niż PO, który właśnie powiedział SELECT c. W tabeli podzapytań / pochodnych.
Aaron Bertrand
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.