เหตุใดแบบสอบถามนี้จึงไม่มีส่วนคำสั่ง FROM ไม่ใช่ข้อผิดพลาด


9

ดังนั้นเราจึงมีแบบสอบถามที่มีคิวรีย่อยที่มีการพิมพ์ผิดอยู่ มันหายไปจากอนุประโยค แต่เมื่อคุณเรียกใช้มันไม่ผิดพลาด! ทำไม!?


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? Nope สิ่งที่เกิดขึ้นคือ SQL Server บอกว่า "โอ้ฉันไม่พบfooที่นี่คุณต้องมีความหมายอีกอย่างหนึ่ง"

NOT INนอกจากนี้ในทั่วไปผมจะหลีกเลี่ยง NOT EXISTSมีศักยภาพที่จะมีประสิทธิภาพมากขึ้นในบางสถานการณ์ NULLแต่ที่สำคัญกว่าพฤติกรรมของมันจะไม่เปลี่ยนแปลงเมื่อเป็นไปได้ว่าคอลัมน์เป้าหมายที่อาจจะ เห็นโพสต์นี้สำหรับข้อมูลเพิ่มเติม



2

ฉันทำซ้ำสิ่งนี้ในปี 2559 ด้วยตัวอย่างง่าย ๆ :

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 คำสั่ง select จะใช้ตาราง "dummy" ซึ่งมีหนึ่งแถวและไม่มีคอลัมน์ ดังนั้น

select 'x' as c where ...

จะส่งคืนหนึ่งแถวถ้านิพจน์เป็นจริงและไม่มีแถวเมื่อมันเป็นเท็จ


แต่นั่นไม่ได้ผลถ้าคุณเพียงแค่พูดselect cและcไม่มีอยู่ในวัตถุชั้นนอกบางอย่าง ผมยอมรับว่าFROMไม่จำเป็นต้องใช้ แต่กลไกในการเล่นที่นี่เมื่อคุณอย่างชัดเจนชื่อคอลัมน์ที่ไม่อยู่ในขอบเขตด้านนอกเป็นมั่นเหมาะที่แตกต่างกว่าตารางหุ่นและถ้าคุณไม่ได้ให้คงที่สำหรับคอลัมน์ที่ไม่ได้เป็น มีอยู่คุณจะได้รับข้อผิดพลาดรันไทม์ดังนั้นจึงไม่มีตารางจำลองที่นั่น ตาราง Dummy สามารถเข้ามาเล่นในสถานการณ์อื่น ๆ ได้ แต่ไม่ใช่เมื่อการอ้างอิงอยู่ในตารางแบบสอบถามย่อย / ที่ได้รับ
Aaron Bertrand

ในตัวอย่างของคุณมันคือ sub-select ที่สัมพันธ์กัน role_id และ role_object_id อยู่ในหนึ่งในตารางในตัวเลือกด้านนอก
Piotr

ถูกต้อง แต่บอกว่าSELECT 'x' AS cเป็นสถานการณ์ที่แตกต่างอย่างสิ้นเชิงกว่าของ OP SELECT cที่เพิ่งกล่าวว่า ในตารางแบบสอบถามย่อย / ที่ได้รับ
Aaron Bertrand
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.