EXISTS (SELECT 1 …) เทียบกับ EXISTS (SELECT * …) อย่างใดอย่างหนึ่งหรือไม่


37

เมื่อใดก็ตามที่ฉันต้องการตรวจสอบการมีอยู่ของแถวในตารางฉันมักจะเขียนเงื่อนไขเช่น:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT *  -- This is what I normally write
          FROM another_table
         WHERE another_table.b = a_table.b
       )

คนอื่นเขียนเหมือน:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT 1   --- This nice '1' is what I have seen other people use
          FROM another_table
         WHERE another_table.b = a_table.b
       )

เมื่อเงื่อนไขNOT EXISTSแทนEXISTS: ในบางโอกาสฉันอาจเขียนด้วยเงื่อนไข a LEFT JOINและพิเศษ (บางครั้งเรียกว่าantijoin ):

SELECT a, b, c
  FROM a_table
       LEFT JOIN another_table ON another_table.b = a_table.b
 WHERE another_table.primary_key IS NULL

ฉันพยายามหลีกเลี่ยงเพราะฉันคิดว่าความหมายนั้นชัดเจนน้อยกว่าโดยเฉพาะอย่างยิ่งเมื่อสิ่งที่คุณprimary_keyไม่ชัดเจนหรือเมื่อคีย์หลักหรือเงื่อนไขการเข้าร่วมของคุณเป็นหลายคอลัมน์ (และคุณสามารถลืมหนึ่งในคอลัมน์ได้อย่างง่ายดาย) อย่างไรก็ตามบางครั้งคุณรักษารหัสที่เขียนโดยคนอื่น ... และมันก็อยู่ที่นั่น

  1. มีความแตกต่าง (นอกเหนือจากสไตล์) เพื่อใช้SELECT 1แทนSELECT *หรือไม่?
    มีกรณีมุมใด ๆ ที่มันไม่ทำงานเหมือนกันหรือไม่?

  2. แม้ว่าสิ่งที่ฉันเขียนคือ SQL มาตรฐาน (AFAIK): มีความแตกต่างสำหรับฐานข้อมูลที่แตกต่างกันหรือเวอร์ชั่นเก่ากว่าหรือไม่?

  3. มีข้อได้เปรียบใด ๆ ในการอธิบายการเขียน antijoin หรือไม่?
    นักวางแผนร่วมสมัย / ออพติไมเซอร์ใช้มันแตกต่างจากNOT EXISTSคำสั่งหรือไม่?


5
โปรดทราบว่า PostgreSQL รองรับการเลือกโดยไม่มีคอลัมน์ดังนั้นคุณจึงสามารถเขียนEXISTS (SELECT FROM ...)ได้
rightfold

1
ฉันได้ถามคำถามเดียวกันเกือบ SO ดังนั้นสองสามปีที่ผ่านมา: stackoverflow.com/questions/7710153/ …
Erwin Brandstetter

คำตอบ:


45

ไม่ไม่มีความแตกต่างของประสิทธิภาพระหว่าง(NOT) EXISTS (SELECT 1 ...)และ(NOT) EXISTS (SELECT * ...)ใน DBMS ที่สำคัญทั้งหมด ฉันมักจะเห็น(NOT) EXISTS (SELECT NULL ...)การใช้เช่นกัน

ในบางครั้งคุณสามารถเขียน(NOT) EXISTS (SELECT 1/0 ...)และผลลัพธ์เหมือนกัน - ไม่มีข้อผิดพลาด (หารด้วยศูนย์) ซึ่งพิสูจน์ว่านิพจน์นั้นไม่มีการประเมินแม้แต่


เกี่ยวกับLEFT JOIN / IS NULLวิธีการ antijoin, การแก้ไข: NOT EXISTS (SELECT ...)นี้จะเทียบเท่ากับ

ในกรณีนี้NOT EXISTSเทียบกับLEFT JOIN / IS NULLคุณอาจได้รับแผนการดำเนินการที่แตกต่างกัน ใน MySQL ตัวอย่างและส่วนใหญ่ในรุ่นเก่า (ก่อน 5.7) แผนจะคล้ายกัน แต่ไม่เหมือนกัน เครื่องมือเพิ่มประสิทธิภาพของ DBMS อื่น ๆ (SQL Server, Oracle, Postgres, DB2) เป็นเท่าที่ฉันรู้ - มากขึ้นหรือน้อยลงความสามารถในการเขียนวิธีการทั้งสองนี้และพิจารณาแผนเดียวกันสำหรับทั้งสอง ยังไม่มีการรับประกันดังกล่าวและเมื่อทำการปรับให้เหมาะสมก็เป็นการดีที่จะตรวจสอบแผนจากการเขียนซ้ำที่แตกต่างกันเนื่องจากอาจมีกรณีที่เครื่องมือเพิ่มประสิทธิภาพแต่ละรายการไม่ได้เขียนซ้ำ (เช่นคำค้นหาที่ซับซ้อนด้วยการรวมจำนวนมาก แบบสอบถามย่อยภายในแบบสอบถามย่อยที่มีเงื่อนไขจากหลายตารางคอลัมน์คอมโพสิตที่ใช้ในเงื่อนไขการเข้าร่วม) หรือตัวเลือกและแผนการเพิ่มประสิทธิภาพจะได้รับผลกระทบแตกต่างกันโดยดัชนีการตั้งค่าที่มีอยู่ ฯลฯ

นอกจากนี้โปรดทราบว่าUSINGไม่สามารถใช้ใน DBMS ทั้งหมด (ตัวอย่างเช่น SQL Server) JOIN ... ONผลงานทั่วไปมากขึ้นทุกที่
และคอลัมน์จะต้องนำหน้าด้วยชื่อตาราง / นามแฝงในSELECTเพื่อหลีกเลี่ยงข้อผิดพลาด / ความคลุมเครือเมื่อเราได้เข้าร่วม
ฉันมักจะชอบที่จะนำคอลัมน์เข้าร่วมในการIS NULLตรวจสอบ (แม้ว่า PK หรือคอลัมน์ที่ไม่ใช่โมฆะจะเป็น OK มันอาจจะเป็นประโยชน์สำหรับประสิทธิภาพเมื่อแผนสำหรับการLEFT JOINใช้ดัชนีที่ไม่ใช่คลัสเตอร์):

SELECT a_table.a, a_table.b, a_table.c
  FROM a_table
       LEFT JOIN another_table 
           ON another_table.b = a_table.b
 WHERE another_table.b IS NULL ;

นอกจากนี้ยังมีวิธีที่สามสำหรับ antijoins โดยใช้NOT INแต่นี่มีความหมายที่แตกต่างกัน (และผลลัพธ์!) หากคอลัมน์ของตารางด้านในเป็นโมฆะ มันสามารถนำมาใช้โดยการยกเว้นแถวด้วยNULLทำให้แบบสอบถามเทียบเท่ากับรุ่น 2 ก่อนหน้านี้:

SELECT a, b, c
  FROM a_table
 WHERE a_table.b NOT IN 
       (SELECT another_table.b
          FROM another_table
         WHERE another_table.b IS NOT NULL
       ) ;

สิ่งนี้มักจะให้ผลแผนการที่คล้ายกันใน DBMS ส่วนใหญ่


1
จนถึงรุ่นล่าสุดมากของ MySQL, [NOT] IN (SELECT ...)แม้ว่าเทียบเท่าดำเนินการมากไม่ดี หลีกเลี่ยงมัน!
Rick James

3
นี้ไม่เป็นความจริงสำหรับ PostgreSQL SELECT *ทำงานได้มากขึ้นอย่างแน่นอน ผมจะให้คำแนะนำสำหรับเห็นแก่ความเรียบง่ายใช้SELECT 1
อีวานคาร์โรลล์

11

มีประเภทหนึ่งของกรณีที่SELECT 1และSELECT *ไม่สามารถแลกเปลี่ยนกันได้ - โดยเฉพาะอย่างยิ่งหนึ่งจะได้รับการยอมรับในกรณีเหล่านั้นในขณะที่คนอื่น ๆ ส่วนใหญ่จะไม่

ฉันกำลังพูดถึงกรณีที่คุณต้องตรวจสอบการมีอยู่ของแถวของชุดที่จัดกลุ่ม หากตารางTมีคอลัมน์C1และC2คุณกำลังตรวจสอบการมีอยู่ของกลุ่มแถวที่ตรงกับเงื่อนไขเฉพาะคุณสามารถใช้SELECT 1สิ่งนี้:

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)

แต่คุณไม่สามารถใช้SELECT *ในลักษณะเดียวกัน

นั่นเป็นเพียงด้านวากยสัมพันธ์ ในกรณีที่ตัวเลือกทั้งสองได้รับการยอมรับ syntactically คุณมักจะมีความแตกต่างในแง่ของประสิทธิภาพหรือผลกลับไม่เป็นอย่างที่ได้รับการอธิบายในคำตอบอื่น

หมายเหตุเพิ่มเติมตามความคิดเห็น

ดูเหมือนว่าผลิตภัณฑ์ฐานข้อมูลไม่มากสนับสนุนความแตกต่างนี้ ผลิตภัณฑ์เช่น SQL Server, Oracle, MySQL และ SQLite ยินดีที่จะยอมรับSELECT *ในแบบสอบถามข้างต้นโดยไม่มีข้อผิดพลาดใด ๆ ซึ่งอาจหมายความว่าพวกเขาปฏิบัติต่อ EXISTS SELECTในรูปแบบพิเศษ

PostgreSQL เป็นหนึ่งใน RDBMS ที่SELECT *อาจล้มเหลว แต่อาจยังทำงานได้ในบางกรณี โดยเฉพาะอย่างยิ่งถ้าคุณจัดกลุ่มโดย PK SELECT *จะทำงานได้ดีมิฉะนั้นจะล้มเหลวด้วยข้อความ:

ข้อผิดพลาด: คอลัมน์ "T.C2" จะต้องปรากฏในกลุ่มตามข้อหรือจะใช้ในฟังก์ชั่นรวม


1
จุดที่ดีแม้ว่านี่ไม่ใช่สิ่งที่ฉันกังวล อันนี้แสดงให้เห็นถึงความแตกต่างทางแนวคิด เพราะเมื่อคุณGROUP BYแนวคิดของ*ไม่มีความหมาย (หรืออย่างน้อยก็ไม่ชัดเจน)
joanolo

5

วิธีที่น่าสนใจในการเขียนEXISTSประโยคที่ทำให้สะอาดและเคียวรีที่ทำให้เข้าใจผิดน้อยลงอย่างน้อยใน SQL Server จะเป็น:

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );

เวอร์ชันต่อต้านกึ่งเข้าร่วมที่มีลักษณะดังนี้:

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );

ทั้งสองมักจะมีการปรับแผนเดียวกับWHERE EXISTSหรือWHERE NOT EXISTSแต่ความตั้งใจที่เป็นแน่แท้และคุณต้องไม่ "แปลก" หรือ1*

ที่น่าสนใจปัญหาการตรวจสอบโมฆะที่เกี่ยวข้องกับNOT IN (...)เป็นปัญหาสำหรับ<> ALL (...)ในขณะที่NOT EXISTS (...)ไม่ประสบปัญหานั้น ลองพิจารณาสองตารางต่อไปนี้ด้วยคอลัมน์ nullable:

IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
    DROP TABLE #t;
END;
CREATE TABLE #t 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
    DROP TABLE #s;
END;
CREATE TABLE #s 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

เราจะเพิ่มข้อมูลลงในทั้งคู่โดยมีแถวที่ตรงกันและบางส่วนที่ไม่:

INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);

SELECT *
FROM #t;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);

SELECT *
FROM #s;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
| 4 | 4 |
+ -------- + ----------- +

NOT IN (...)แบบสอบถาม:

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );

มีแผนดังต่อไปนี้:

ป้อนคำอธิบายรูปภาพที่นี่

แบบสอบถามไม่ส่งคืนแถวเนื่องจากค่า NULL ทำให้ไม่สามารถยืนยันความเท่าเทียมกันได้

แบบสอบถามนี้พร้อม<> ALL (...)แสดงแผนเดียวกันและส่งคืนแถว:

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );

ป้อนคำอธิบายรูปภาพที่นี่

การใช้ชุดตัวเลือกNOT EXISTS (...)แสดงรูปร่างของแผนแตกต่างกันเล็กน้อยและส่งคืนแถว:

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );

แผนการ:

ป้อนคำอธิบายรูปภาพที่นี่

ผลลัพธ์ของแบบสอบถามนั้น:

+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +

นี้จะทำให้การใช้เช่นเดียวกับที่มีแนวโน้มที่จะเป็นผลที่มีปัญหา<> ALL (...)NOT IN (...)


3
ผมต้องบอกว่าฉันไม่พบ*ว่าเป็นที่แปลก: ผมอ่านASEXISTS (SELECT * FROM t WHERE ...) there is a _row_ in table _t_ that...อย่างไรก็ตามฉันชอบที่จะมีทางเลือกอื่นและคุณอ่านได้ชัดเจน หนึ่งข้อสงสัย / ข้อแม้: มันจะทำงานอย่างไรถ้าbเป็นโมฆะ? [ฉันมีประสบการณ์ที่ไม่ดีและมีคืนสั้น ๆ เมื่อพยายามค้นหาความผิดพลาดที่เกิดจากx IN (SELECT something_nullable FROM a_table)]
joanolo

EXISTS จะบอกคุณว่าตารางมีแถว & ส่งคืนค่าจริงหรือเท็จ EXISTS (SELECT x FROM (ค่า (null)) เป็นจริง IN คือ = อะไรก็ได้ & ไม่ใช่ IN คือ <> ALL ทั้ง 4 ตัวนี้ใช้แถว RHS ที่มีค่า NULL ให้ตรงกัน (x) = ANY (ค่า (null)) & (x) <> ALL (ค่า (null)) ไม่เป็นที่รู้จัก / เป็นโมฆะ แต่ EXISTS (ค่า (null)) เป็นจริง (IN & = ใด ๆ มีปัญหาการตรวจสอบ null ที่เกี่ยวข้องกับ NOT IN (... ) [& ] <> ALL (... ) ". & ซ้ำทุกอย่างหรือ & และ แต่มี" ปัญหา "เท่านั้นหากคุณไม่จัดระเบียบความหมายตามที่ตั้งใจไว้) อย่าแนะนำให้ใช้สิ่งเหล่านี้กับ EXISTS ไม่ใช่ "ทำให้เข้าใจผิดน้อยลง"
philipxy

@philliprxy - ถ้าฉันผิดฉันก็ไม่มีปัญหาที่จะยอมรับมัน รู้สึกอิสระที่จะเพิ่มคำตอบของคุณเองถ้าคุณรู้สึกว่ามัน
Max Vernon

4

"พิสูจน์" ว่าพวกเขาเหมือนกัน (ใน MySQL) คือการทำ

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;

SELECT 1จากนั้นทำซ้ำกับ ในทั้งสองกรณี 'ขยาย' SELECT 1แสดงให้เห็นว่าการส่งออกว่ามันก็กลายเป็น

ในทำนองเดียวกันจะกลายเป็นCOUNT(*)COUNT(0)

สิ่งที่ควรทราบอีกประการ: มีการปรับปรุงการเพิ่มประสิทธิภาพในรุ่นล่าสุด มันอาจจะคุ้มค่าเมื่อเทียบEXISTSกับการต่อต้านการเข้าร่วม เวอร์ชันของคุณอาจทำงานได้ดีกว่าเมื่อเทียบกับอีกรุ่นหนึ่ง


4

ในบางฐานข้อมูลการปรับแต่งนี้ยังใช้งานไม่ได้ เช่นอินสแตนซ์ใน PostgreSQLตั้งแต่เวอร์ชัน 9.6 สิ่งนี้จะล้มเหลว

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT *
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

และนี่จะประสบความสำเร็จ

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT 1  -- This changed from the first query
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

มันล้มเหลวเพราะสิ่งต่อไปนี้ล้มเหลว

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;

คุณสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับการเล่นโวหารนี้โดยเฉพาะและการละเมิดข้อมูลจำเพาะในคำตอบของฉันสำหรับคำถามSQL Spec ต้องการกลุ่มตามใน EXISTS () หรือไม่


กรณีที่หายากมุมเล็ก ๆ น้อย ๆที่แปลกบางที แต่อีกครั้งพิสูจน์ว่าคุณมีการทำจำนวนมากประนีประนอมเมื่อออกแบบฐานข้อมูล ...
joanolo

-1

ฉันใช้เสมอselect top 1 'x'(SQL Server)

ในทางทฤษฎีselect top 1 'x'จะมีประสิทธิภาพมากกว่านั้นselect *ในขณะที่อดีตจะเสร็จสมบูรณ์หลังจากเลือกค่าคงที่ในการมีอยู่ของแถวที่มีคุณสมบัติในขณะที่หลังจะเลือกทุกอย่าง

อย่างไรก็ตามถึงแม้จะเร็ว แต่ก็มีความเกี่ยวข้อง แต่การปรับให้เหมาะสมได้สร้างความแตกต่างที่ไม่เกี่ยวข้องใน RDBS สำคัญ ๆ ทั้งหมด


มีเหตุผล. นั่นอาจเป็น (หรืออาจเป็นไปได้) หนึ่งในไม่กี่กรณีที่top nไม่มีorder byความคิดที่ดี
joanolo

3
"ตามหลักวิชา, .... " ไม่ในทางทฤษฎีselect top 1 'x'ไม่ควรมีประสิทธิภาพมากกว่าselect *ในการExistแสดงออก ในทางปฏิบัติอาจมีประสิทธิภาพมากกว่าหากเครื่องมือเพิ่มประสิทธิภาพทำงานได้ดีกว่า แต่ในทางทฤษฎีแล้วนิพจน์ทั้งสองนั้นเทียบเท่ากัน
miracle173

-4

IF EXISTS(SELECT TOP(1) 1 FROMเป็นนิสัยที่ดีกว่าในระยะยาวและข้ามแพลตฟอร์มเพียงเพราะคุณไม่จำเป็นต้องกังวลเกี่ยวกับความดีหรือความเลวของแพลตฟอร์ม / เวอร์ชันปัจจุบันของคุณ และ SQL กำลังเปลี่ยนจากTOP nไปสู่การปรับเปลี่ยนพารามิเตอร์TOP(n)ได้ นี่ควรเป็นทักษะการเรียนรู้ครั้งเดียว


3
คุณหมายความว่าอย่างไรกับ"ข้ามแพลตฟอร์ม" ? TOPไม่ได้เป็น SQL ที่ถูกต้อง
ypercubeᵀᴹ

"SQL กำลังเคลื่อนที่ .. " ผิดปกติ ไม่มีTOP (n)ใน "SQL" - ภาษาแบบสอบถามมาตรฐาน มีอยู่บน T-SQL ซึ่งเป็นภาษาถิ่นของ Microsoft SQL Server ที่ใช้อยู่
a_horse_with_no_name

แท็กของคำถามเดิมคือ "SQL Server" แต่มันก็โอเคที่จะ downvote และโต้แย้งสิ่งที่ฉันพูด - มันเป็นวัตถุประสงค์ของเว็บไซต์นี้เพื่อเปิดใช้งาน downvoting ง่าย ฉันจะฝนตกในขบวนแห่ของคุณด้วยความสนใจที่น่าเบื่อในรายละเอียด?
ajeh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.