Mengapa kueri ini, melewatkan klausa DARI, tidak keluar?

9

Jadi kami memiliki kueri dengan subquery yang memiliki kesalahan ketik di dalamnya. Tidak ada klausa FROM. Tetapi ketika Anda menjalankannya, itu tidak salah! Mengapa!?


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
sumber

Jawaban:

21

Pernyataan ini sah (dengan kata lain, tidak FROMdiperlukan):

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

Kuncinya adalah ketika Anda memperkenalkan nama kolom yang jelas tidak bisa ada. Jadi ini gagal:

SELECT name WHERE 1 = 1;

SELECT x = 1 WHERE id > 0;

Msg 207, Level 16, Negara 1
Nama kolom 'nama' tidak valid.
Msg 207, Level 16, Negara 1
Nama kolom 'id' tidak valid.

Tetapi ketika kolom yang tidak valid diperkenalkan dalam sesuatu seperti subquery, apa yang SQL Server lakukan ketika tidak dapat menemukan kolom di lingkup dalam subquery, dilintasi ke lingkup luar, dan membuat subquery berkorelasi dengan lingkup luar itu. Ini akan mengembalikan semua baris, misalnya:

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

Karena pada dasarnya dikatakan:

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

Anda bahkan tidak perlu WHEREklausa dalam subquery:

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

Anda dapat melihat bahwa itu benar-benar melihat ke luar pada meja yang dicakup, karena ini:

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

Mengembalikan baris yang jauh lebih sedikit (11 di sistem saya).

Ini melibatkan kepatuhan terhadap standar tentang pelingkupan. Anda dapat melihat hal-hal serupa ketika Anda memiliki dua tabel #temp:

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

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

Jelas, ini kesalahan, kan, karena tidak ada foodi #bar? Nggak. Apa yang terjadi adalah bahwa SQL Server berkata, "oh, saya tidak menemukan di foosini, Anda pasti maksud yang lain."

Juga, secara umum, saya akan menghindari NOT IN. NOT EXISTSmemiliki potensi untuk menjadi lebih efisien dalam beberapa skenario, tetapi yang lebih penting, perilakunya tidak berubah ketika mungkin bahwa kolom target bisa NULL. Lihat posting ini untuk info lebih lanjut .

Aaron Bertrand
sumber
Saya mengajukan pertanyaan tentang Stack Overflow yang jawabannya pada dasarnya sama dengan ini (meskipun Anda lebih teliti). Mengapa mereferensikan kolom (sebagai operan di sebelah kiri) yang bukan bagian dari tabel yang ditanyakan bukan kesalahan di operator EXISTS?
Marc.2377
2

Saya mereproduksi ini pada tahun 2016 dengan contoh sederhana:

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)

Tampaknya c2 dan c3 dievaluasi untuk setiap baris.

paulbarbin
sumber
1

Dalam SQL Server SELECT sintaks tidak memerlukan bagian DARI. Jika Anda menghilangkan FROM, pernyataan pilih akan menggunakan tabel "dummy" yang memiliki satu baris dan tidak ada kolom. Begitu

select 'x' as c where ...

akan mengembalikan satu baris jika ekspresi itu benar dan tidak ada baris ketika itu salah.

Piotr
sumber
Tapi itu tidak berfungsi jika Anda hanya mengatakan select cdan ctidak ada di beberapa objek luar. Saya setuju bahwa FROMtidak diperlukan, tetapi mekanisme yang dimainkan di sini ketika Anda secara eksplisit menyebutkan kolom yang memang ada di lingkup luar pasti berbeda dari tabel dummy, dan jika Anda tidak memberikan konstanta untuk kolom yang tidak ada, Anda mendapatkan kesalahan runtime, jadi tidak ada tabel dummy di sana. Tabel Dummy dapat ikut bermain dalam skenario lain, tetapi tidak ketika referensi berada dalam tabel subquery / turunan.
Aaron Bertrand
Dalam contoh Anda itu adalah sub-pilih berkorelasi, role_id dan role_object_id milik salah satu tabel di pilih luar.
Piotr
Benar, tetapi mengatakan SELECT 'x' AS cadalah skenario yang sama sekali berbeda dari skenario OP, yang baru saja mengatakan SELECT c. Dalam tabel subquery / turunan.
Aaron Bertrand