SQL WHERE .. IN อนุประโยคหลายคอลัมน์


173

ฉันต้องใช้แบบสอบถามต่อไปนี้ใน SQL Server:

select *
from table1
WHERE  (CM_PLAN_ID,Individual_ID)
IN
(
 Select CM_PLAN_ID, Individual_ID
 From CRM_VCM_CURRENT_LEAD_STATUS
 Where Lead_Key = :_Lead_Key
)

แต่ส่วนคำสั่ง WHERE ..IN อนุญาตเพียง 1 คอลัมน์เท่านั้น ฉันจะเปรียบเทียบคอลัมน์ 2 คอลัมน์ขึ้นไปกับ SELECT ภายในอื่นได้อย่างไร


ฉันพยายามที่จะให้ภาพรวมของการแก้ปัญหาที่เกี่ยวข้องพร้อมกับข้อควรระวังที่จำเป็น: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin

คำตอบ:


110

คุณสามารถสร้างตารางที่ได้รับจากแบบสอบถามย่อยและเข้าร่วม table1 กับตารางที่ได้รับนี้:

select * from table1 LEFT JOIN 
(
   Select CM_PLAN_ID, Individual_ID
   From CRM_VCM_CURRENT_LEAD_STATUS
   Where Lead_Key = :_Lead_Key
) table2
ON 
   table1.CM_PLAN_ID=table2.CM_PLAN_ID
   AND table1.Individual=table2.Individual
WHERE table2.CM_PLAN_ID IS NOT NULL

7
หรืออื่น ๆ โดยทั่วไป SELECT * FROM ตาราง INNER JOIN ON otherTable (table.x = otherTable.a และ table.y = otherTable.b)
Ala

4
สิ่งที่เกี่ยวกับหลายแถวที่จะมีอยู่ถ้าตาราง 2 เป็นลูกของตารางที่ 1 และทำไมซ้ายเข้าร่วม
gbn

1
ใช่เข้าร่วมภายในจะเป็นนักแสดงที่นี่มากขึ้น ทำ LEFT JOIN และกรองค่า Null จากตารางที่ 2 เป็นเพียงวิธีการใช้ INNER JOIN
Pstr

ผิดนี่จะให้แถวหลายครั้งโดยสมมติว่าตารางที่เข้าร่วมสามารถเข้าร่วมได้หลายครั้ง ... มิฉะนั้นเข้าร่วมภายในและคุณสามารถสำรองตำแหน่งของตัวเองได้
Stefan Steiger

123

คุณจะต้องใช้ไวยากรณ์ WHERE EXISTS แทน

SELECT *
FROM table1
WHERE EXISTS (SELECT *
              FROM table2
              WHERE Lead_Key = @Lead_Key
                        AND table1.CM_PLAN_ID = table2.CM_PLAN_ID
                        AND table1.Individual_ID = table2.Individual_ID)

5
ขณะนี้จะใช้งานได้ แต่จะแปลงคิวรีที่ไม่เกี่ยวข้องในคำถามให้เป็นคิวรีที่สัมพันธ์กัน เว้นแต่จะเพิ่มประสิทธิภาพการค้นหาเป็นคนฉลาดนี้อาจทำให้คุณ O (n ^ 2) ประสิทธิภาพ :-( แต่บางทีฉันประเมินเพิ่มประสิทธิภาพ ... .
sleske

1
ฉันใช้ไวยากรณ์แบบนี้ตลอดเวลาโดยไม่มีปัญหา นอกจากว่าคุณกำลังใช้เครื่องมือเพิ่มประสิทธิภาพรุ่นเก่า (6.5, 7, 8, ฯลฯ ) มันไม่ควรมีปัญหากับไวยากรณ์นี้
mrdenny

1
@sleske: EXISTS ดีกว่ามาก: ดูความคิดเห็นของฉันในคำตอบของฉัน และทดสอบก่อน @mrdenny: ผมผิดคำตอบของคุณในตอนแรกฉันต้องการใช้ EXISTS เกินไป
GBN

6
สิ่งนี้มีประสิทธิภาพมากที่สุด +1 ดูบทความนี้ในบล็อกของฉันสำหรับการเปรียบเทียบผลการดำเนินงาน: explainextended.com/2009/06/17/efficient-exists
Quassnoi

1
แม้แต่ SQL 2000 ก็สามารถจัดการเคียวรีย่อยที่สัมพันธ์กันโดยไม่ต้องเปลี่ยนเคียวรีเป็น O (n ^ 2) อาจเป็นปัญหากลับมาที่ 6.5
GilaMonster

14

คำเตือนเกี่ยวกับการแก้ปัญหา:

โซลูชั่นที่มีอยู่มากมายจะให้ผลลัพธ์ที่ไม่ถูกต้องหากแถวไม่ได้มีลักษณะเฉพาะ

หากคุณเป็นคนเดียวที่สร้างตารางสิ่งนี้อาจไม่เกี่ยวข้องกัน แต่โซลูชันหลายอย่างจะให้จำนวนแถวที่แตกต่างจากรหัสที่เป็นปัญหาเมื่อหนึ่งในตารางอาจไม่มีแถวที่ไม่ซ้ำกัน

คำเตือนเกี่ยวกับงบปัญหา:

ด้วยคอลัมน์หลายอันนั้นไม่มีอยู่ลองคิดดูว่าคุณต้องการอะไรอย่างระมัดระวัง

เมื่อฉันเห็นคอลัมน์สองคอลัมน์ฉันสามารถจินตนาการได้ว่ามันหมายถึงสองสิ่ง:

  1. ค่าของคอลัมน์ a และคอลัมน์ b ปรากฏในตารางอื่นอย่างอิสระ
  2. ค่าของคอลัมน์ a และคอลัมน์ b ปรากฏในตารางอื่นพร้อมกันในแถวเดียวกัน

สถานการณ์ที่ 1 ค่อนข้างน่าสนใจเพียงใช้สองข้อความสั่ง

เพื่อให้สอดคล้องกับคำตอบที่มีอยู่ส่วนใหญ่ฉันขออธิบายภาพรวมของวิธีการที่กล่าวถึงและเพิ่มเติมสำหรับสถานการณ์จำลอง 2 (และการตัดสินโดยย่อ):

EXISTS (ปลอดภัยแนะนำสำหรับ SQL Server)

จัดหาโดย @mrdenny EXISTS ฟังดูเหมือนสิ่งที่คุณกำลังมองหานี่คือตัวอย่างของเขา:

SELECT * FROM T1
WHERE EXISTS
(SELECT * FROM T2 
 WHERE T1.a=T2.a and T1.b=T2.b)

ซ้าย SEMI เข้าร่วม (ปลอดภัยแนะนำสำหรับภาษาที่รองรับ)

นี่เป็นวิธีที่รัดกุมมากในการเข้าร่วม แต่น่าเสียดายที่ภาษา SQL ส่วนใหญ่รวมถึงเซิร์ฟเวอร์ SQL ไม่สนับสนุนในปัจจุบัน

SELECT * FROM T1
LEFT SEMI JOIN T2 ON T1.a=T2.a and T1.b=T2.b

คำสั่ง IN จำนวนมาก (ปลอดภัย แต่ระวังการทำสำเนารหัส)

ดังที่ @cataclysm ที่ใช้คำสั่ง IN สองข้อสามารถทำเคล็ดลับได้เช่นกันบางทีมันอาจจะดีกว่าโซลูชันอื่น ๆ อย่างไรก็ตามสิ่งที่คุณควรระวังอย่างยิ่งคือการทำสำเนารหัส หากคุณต้องการเลือกจากตารางอื่นหรือเปลี่ยนคำสั่ง where เป็นความเสี่ยงที่เพิ่มขึ้นที่คุณสร้างความไม่สอดคล้องกันในตรรกะของคุณ

โซลูชันพื้นฐาน

SELECT * from T1
WHERE a IN (SELECT a FROM T2 WHERE something)
AND b IN (SELECT b FROM T2 WHERE something)

โซลูชันที่ไม่มีการทำสำเนารหัส (ฉันเชื่อว่านี่จะไม่ทำงานในการสืบค้น SQL Server ปกติ)

WITH mytmp AS (SELECT a, b FROM T2 WHERE something);
SELECT * from T1 
WHERE a IN (SELECT a FROM mytmp)
AND b IN (SELECT b FROM mytmp)

เข้าร่วมภายใน (ในทางเทคนิคสามารถทำให้ปลอดภัย แต่บ่อยครั้งที่สิ่งนี้ไม่ได้ทำ)

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

SELECT T1.* FROM T1
INNER JOIN 
(SELECT DISTINCT a, b FROM T2) AS T2sub
ON T1.a=T2sub.a AND T1.b=T2sub.b

ข้อผิดพลาดที่พบบ่อยที่สุด:

  1. เข้าร่วมโดยตรงกับ T2 โดยไม่มีแบบสอบถามย่อยที่ปลอดภัย ส่งผลให้เกิดความเสี่ยงในการทำซ้ำ)
  2. SELECT * (รับประกันเพื่อรับคอลัมน์จาก T2)
  3. เลือก c (ไม่รับประกันว่าคอลัมน์ของคุณจะมาจาก T1 เสมอ)
  4. ไม่มี DISTINCT หรือ DISTINCT ในตำแหน่งที่ไม่ถูกต้อง

การจัดเรียงของคอลัมน์กับตัวแยก (ไม่ปลอดภัยมากประสิทธิภาพที่น่ากลัว)

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

SELECT * FROM T1
WHERE CONCAT(a,"_",b) IN 
(SELECT CONCAT(a,"_",b) FROM T2)

โปรดทราบว่าหากคอลัมน์ของคุณเป็นตัวเลขภาษา SQL บางภาษาจะต้องให้คุณแปลงเป็นสตริงก่อน ฉันเชื่อว่าเซิร์ฟเวอร์ SQL จะทำสิ่งนี้โดยอัตโนมัติ


ในการสรุป: ตามปกติมีหลายวิธีในการใช้ SQL การใช้ตัวเลือกที่ปลอดภัยจะช่วยหลีกเลี่ยงการแปลกใหม่และประหยัดเวลาและส่วนหัวในระยะยาว


13
select * from tab1 where (col1,col2) in (select col1,col2 from tab2)

หมายเหตุ:
Oracle จะข้ามแถวที่มีคอลัมน์ที่เลือกอย่างน้อยหนึ่งคอลัมน์เป็น NULL ในกรณีเหล่านี้คุณอาจต้องการใช้ประโยชน์จากNVL -Funktion เพื่อแมป NULL ให้เป็นค่าพิเศษ (ซึ่งไม่ควรอยู่ในค่า)

select * from tab1
where (col1, NVL(col2, '---') in (select col1, NVL(col2, '---') from tab2)

2
รองรับ postgres where (colA,colB) in (... some list of tuples...)แต่ฉันไม่แน่ใจว่าฐานข้อมูลอื่นทำอะไรได้บ้าง ฉันสนใจที่จะรู้
Max Murphy

2
รองรับไวยากรณ์นี้ใน Oracle และ DB2 / 400 เช่นกัน (อาจเป็น DB2 ด้วย) ต้องการให้ SQL Server รองรับ
CrazyIvan1974

DB2 รองรับสิ่งนี้
Telmo Marques

แม้แต่ SQLite ก็ยังรองรับ
Holger Jakobs

13

ประโยค EXISTS ง่าย ๆ เรียบง่ายที่สุด

select *
from table1 t1
WHERE
EXISTS
(
 Select * --or 1. No difference...
 From CRM_VCM_CURRENT_LEAD_STATUS Ex
 Where Lead_Key = :_Lead_Key
-- correlation here...
AND
t1.CM_PLAN_ID = Ex.CM_PLAN_ID AND t1.CM_PLAN_ID =  Ex.Individual_ID
)

หากคุณมีหลายแถวในสหสัมพันธ์ดังนั้น JOIN จะให้หลายแถวในผลลัพธ์ดังนั้นคุณต้องแตกต่างกัน ซึ่งมักทำให้ EXISTS มีประสิทธิภาพมากขึ้น

หมายเหตุSELECT *ด้วย JOIN จะรวมคอลัมน์จากตารางที่ จำกัด แถวด้วย


2

เหตุใดจึงต้องใช้ WHERE EXISTS หรือ DERIVED TABLES เมื่อคุณสามารถเข้าร่วมวงใน:

SELECT t.*
FROM table1 t
INNER JOIN CRM_VCM_CURRENT_LEAD_STATUS s
    ON t.CM_PLAN_ID = s.CM_PLAN_ID
    AND t.Individual_ID = s.Individual_ID
WHERE s.Lead_Key = :_Lead_Key

หากคู่ของ (CM_PLAN_ID, Individual_ID) ไม่ซ้ำกันในตารางสถานะคุณอาจต้องการ SELECT DISTINCT t. * แทน


3
และแตกต่างมักจะหมายถึงการมีอยู่มีประสิทธิภาพมากขึ้น
GBN

0
Postgres SQL  : version 9.6
Total records on tables : mjr_agent = 145, mjr_transaction_item = 91800

1. ใช้งานด้วยEXISTS[เวลาแบบสอบถามเฉลี่ย: 1.42 วินาที]

SELECT count(txi.id) 
FROM 
mjr_transaction_item txi
WHERE 
EXISTS ( SELECT 1 FROM mjr_agent agnt WHERE agnt.agent_group = 0 AND (txi.src_id = agnt.code OR txi.dest_id = agnt.code) ) 

2. การใช้สองบรรทัดINข้อ [เวลาเฉลี่ยในการค้นหา: 0.37 วินาที]

SELECT count(txi.id) FROM mjr_transaction_item txi
WHERE 
txi.src_id IN ( SELECT agnt.code FROM mjr_agent agnt WHERE agnt.agent_group = 0 ) 
OR txi.dest_id IN ( SELECT agnt.code FROM mjr_agent agnt WHERE agnt.agent_group = 0 )

3. การใช้INNNER JOINรูปแบบ [เวลาเฉลี่ยโดยเฉลี่ย: 2.9 วินาที]

SELECT count(DISTINCT(txi.id)) FROM mjr_transaction_item txi
INNER JOIN mjr_agent agnt ON agnt.code = txi.src_id OR agnt.code = txi.dest_id
WHERE 
agnt.agent_group = 0

ดังนั้นฉันเลือกตัวเลือกที่สอง


คำเตือนสำหรับผู้อ่านในอนาคต: ตามคำถามคุณอาจต้องการใช้ANDข้อความสั่งแทนที่จะเป็นORข้อความ
Dennis Jaheruddin

@DennisJaheruddin .. ขอบคุณสำหรับความคิดเห็นและคำอธิบายรายละเอียดที่ดีมากสำหรับคำตอบของคุณ คุณถูกต้องแล้วORข้อความสั่งอาจทำให้เกิดการซ้ำซ้อน ในกรณีของฉันไม่มีแถวที่มีเหมือนกันsrc_idและdest_idในแถวเดียว ดังนั้นการทำซ้ำจะไม่เกิดขึ้นในกรณีของฉัน
Cataclysm


-2

หากคุณต้องการตารางหนึ่งให้ใช้แบบสอบถามต่อไปนี้

SELECT S.* 
FROM Student_info S
  INNER JOIN Student_info UT
    ON S.id = UT.id
    AND S.studentName = UT.studentName
where S.id in (1,2) and S.studentName in ('a','b')

และข้อมูลตารางดังต่อไปนี้

id|name|adde|city
1   a   ad  ca
2   b   bd  bd
3   a   ad  ad
4   b   bd  bd
5   c   cd  cd

จากนั้นส่งออกดังนี้

id|name|adde|city
1   a   ad  ca
2   b   bd  bd

id in (1,2) and studentName in ('a','b')ไม่เหมือนกัน(id, studentName) in ((1,'a'),(2,'b'))ทั้งหมด แค่คิดว่าระเบียนมี id = 2 และ name = 'a' แน่นอนถ้า ID ไม่ซ้ำกันผลจะลดลง แต่ถ้า ID ไม่ซ้ำกันเราไม่จำเป็นต้องกรองชื่อเลย
quetzalcoatl

-2

เราสามารถทำสิ่งนี้ได้ง่ายๆ

   select *
   from 
    table1 t, CRM_VCM_CURRENT_LEAD_STATUS c
    WHERE  t.CM_PLAN_ID = c.CRM_VCM_CURRENT_LEAD_STATUS
    and t.Individual_ID = c.Individual_ID

-2

การเชื่อมคอลัมน์เข้าด้วยกันในบางรูปแบบคือ "แฮ็ค" แต่เมื่อผลิตภัณฑ์ไม่รองรับการรวมกึ่งคอลัมน์มากกว่าหนึ่งคอลัมน์บางครั้งคุณก็ไม่มีทางเลือก

ตัวอย่างที่โซลูชันการรวมภายใน / ภายนอกเข้าร่วมไม่ทำงาน:

select * from T1 
 where <boolean expression>
   and (<boolean expression> OR (ColA, ColB) in (select A, B ...))
   and <boolean expression>
   ...

เมื่อแบบสอบถามไม่สำคัญในบางครั้งคุณไม่สามารถเข้าถึงตารางฐานเพื่อทำการรวมภายใน / ภายนอกเข้าร่วมได้

หากคุณใช้ "แฮ็ค" นี้เมื่อคุณรวมฟิลด์ต่าง ๆ ต้องแน่ใจว่าได้เพิ่มตัวคั่นในฟิลด์ไว้พอเพื่อหลีกเลี่ยงการตีความที่ผิดเช่น ColA + ":-:" + ColB


คำตอบนี้ดูเหมือนจะไม่สอดคล้องกัน (กล่าวถึงการต่อกันแล้วนำเสนออีกตัวอย่างหนึ่ง) นอกจากนี้ในโน้ตที่เบากว่า: เรามีตัวเลือกเสมอ ;-) ฉันเพิ่มตัวอย่างการเรียงต่อกันลงในภาพรวมของฉันที่นี่พร้อมเชิงอรรถที่เกี่ยวข้อง: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin

-3

ฉันก่อตั้งได้ง่ายขึ้นด้วยวิธีนี้

Select * 
from table1 
WHERE  (convert(VARCHAR,CM_PLAN_ID) + convert(VARCHAR,Individual_ID)) 
IN 
(
 Select convert(VARCHAR,CM_PLAN_ID) + convert(VARCHAR,Individual_ID)
 From CRM_VCM_CURRENT_LEAD_STATUS 
 Where Lead_Key = :_Lead_Key 
) 

หวังว่าความช่วยเหลือนี้ :)


9
อย่างไรก็ตาม, ไม่มีการใช้ดัชนีที่นี่ทำกับสตริง concat
mrdenny

9
ฉันลงคะแนนให้เพราะมันอันตรายธรรมดา! ถ้าCM_PLAN_ID = 45และIndividual_ID = 3จากนั้นผลการเรียงต่อกันใน453- ซึ่งแยกไม่ออกจากกรณีที่CM_PLAN_ID = 4และIndividual_ID = 53... ถามหาปัญหาฉันจะคิด
El Ronnoco

5
.. แน่นอนว่าคุณสามารถต่อกับ char แบบพิเศษตามอำเภอใจ45_3หรือ45:3แต่มันก็ไม่ใช่ทางออกที่ดีและแน่นอนว่า @mrdenny บอกว่าดัชนีจะไม่ถูกนำไปใช้ในขณะที่การแปลงเกิดขึ้นในคอลัมน์
El Ronnoco

1
ฉันลงคะแนนด้วยเช่นกันเนื่องจากโซลูชันนี้เป็น "แฮ็ค" ที่รวดเร็วเท่านั้น มันช้าและดังที่ El Ronnoco กล่าวว่ามันสามารถนำไปสู่ข้อบกพร่องได้

-4

วิธีที่ง่ายและไม่ถูกต้องจะรวมสองคอลัมน์โดยใช้ + หรือต่อกันแล้วสร้างหนึ่งคอลัมน์

Select *
from XX
where col1+col2 in (Select col1+col2 from YY)

นี่จะเป็นสนามที่ค่อนข้างช้า ไม่สามารถใช้ในการเขียนโปรแกรม แต่ถ้าในกรณีที่คุณเพียงแค่สอบถามเพื่อตรวจสอบสิ่งที่อาจจะใช้


10
แน่นอนและสามารถนำไปสู่ข้อผิดพลาดเนื่องจากเช่น 'ab' + 'c' = 'a' + 'bc'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.