Tại sao truy vấn này, thiếu một mệnh đề TỪ, không có lỗi?


9

Vì vậy, chúng tôi có một truy vấn với một truy vấn con có lỗi đánh máy trong đó. Nó thiếu mệnh đề TỪ. Nhưng khi bạn chạy nó, nó không lỗi! Tại sao!?


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'

Câu trả lời:


21

Tuyên bố này là hợp pháp (nói cách khác, không FROMbắt buộc):

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

Bí quyết là khi bạn giới thiệu một tên cột rõ ràng không thể tồn tại. Vì vậy, những thất bại:

SELECT name WHERE 1 = 1;

SELECT x = 1 WHERE id > 0;

Msg 207, Cấp 16, Bang 1
Tên cột 'không hợp lệ'.
Msg 207, Cấp 16, Trạng thái 1
Tên cột không hợp lệ 'id'.

Nhưng khi cột không hợp lệ được giới thiệu trong một cái gì đó giống như truy vấn con, SQL Server sẽ làm gì khi không thể tìm thấy cột đó trong phạm vi bên trong của truy vấn con, đi qua phạm vi bên ngoài và làm cho truy vấn con tương quan với phạm vi bên ngoài đó. Điều này sẽ trả về tất cả các hàng, ví dụ:

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

Bởi vì về cơ bản nó nói:

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

Bạn thậm chí không cần một WHEREmệnh đề trong truy vấn con:

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

Bạn có thể thấy rằng nó thực sự nhìn vào bảng có phạm vi bên ngoài, bởi vì điều này:

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

Trả về hàng ít hơn nhiều (11 trên hệ thống của tôi).

Điều này liên quan đến việc tuân thủ các tiêu chuẩn về phạm vi. Bạn có thể thấy những điều tương tự khi bạn có hai bảng #temp:

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

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

Rõ ràng, điều này nên lỗi, phải, vì không có footrong #bar? Không. Điều xảy ra là SQL Server nói, "ồ, tôi không tìm thấy fooở đây, bạn phải có ý nghĩa khác."

Ngoài ra, nói chung, tôi sẽ tránh NOT IN. NOT EXISTScó khả năng hiệu quả hơn trong một số tình huống, nhưng quan trọng hơn, hành vi của nó không thay đổi khi có thể cột mục tiêu có thể NULL. Xem bài đăng này để biết thêm .


Tôi đã hỏi một câu hỏi về Stack Overflow mà câu trả lời về cơ bản giống như thế này (mặc dù câu hỏi của bạn kỹ lưỡng hơn). Tại sao tham chiếu một cột (dưới dạng toán hạng bên trái) không phải là một phần của bảng được truy vấn không phải là lỗi trong toán tử EXISTS?
Marc.2377

2

Tôi đã tái tạo điều này vào năm 2016 với một ví dụ đơn giản:

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)

Dường như c2 và c3 được ước tính cho mỗi hàng.


1

Trong SQL Server CHỌN cú pháp không yêu cầu phần TỪ. Nếu bạn bỏ qua TỪ, chọn câu lệnh sẽ sử dụng bảng "giả" có một hàng và không có cột. Vì thế

select 'x' as c where ...

sẽ trả về một hàng nếu biểu thức là đúng và không có hàng nào khi nó sai.


Nhưng điều đó không hiệu quả nếu bạn chỉ nói select cckhông tồn tại trong một số đối tượng bên ngoài. Tôi đồng ý rằng FROMlà không bắt buộc, nhưng các cơ chế chơi ở đây khi bạn rõ ràng tên một cột đó không tồn tại trong một phạm vi bên ngoài chắc chắn khác với một bảng giả, và nếu bạn không cung cấp một hằng số cho một cột mà không làm tồn tại, bạn nhận được một lỗi thời gian chạy, vì vậy không có bảng giả ở đó. Bảng giả có thể xuất hiện trong các tình huống khác, nhưng không phải khi tham chiếu nằm trong bảng phụ / bảng dẫn xuất.
Aaron Bertrand

Trong ví dụ của bạn, nó là một lựa chọn phụ tương quan, Role_id và role_object_id thuộc về một trong các bảng trong lựa chọn bên ngoài.
Piotr

Đúng, nhưng nói SELECT 'x' AS clà một kịch bản hoàn toàn khác so với OP, người vừa nói SELECT c. Trong một bảng phụ / dẫn xuất.
Aaron Bertrand
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.