SQL WHERE .. IN mệnh đề nhiều cột


173

Tôi cần triển khai truy vấn sau trong 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
)

Nhưng mệnh đề WHERE..IN chỉ cho phép 1 cột. Làm cách nào tôi có thể so sánh 2 hoặc nhiều cột với một CHỌN bên trong khác?


Tôi đã cố gắng cung cấp một cái nhìn tổng quan về các giải pháp có liên quan, với các cảnh báo cần thiết ở đây: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin

Câu trả lời:


110

Bạn có thể tạo một bảng dẫn xuất từ ​​truy vấn con và nối bảng1 với bảng dẫn xuất này:

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
hoặc nói chung là CHỌN * TỪ bảng INNER THAM GIA otherTable ON (bảng.x = otherTable.a AND table.y = otherTable.b)
ala

4
Điều gì về nhiều hàng sẽ tồn tại nếu bảng 2 là con của bảng 1? Và tại sao TRÁI PHẢI THAM GIA?
gbn

1
Vâng, INNER THAM GIA sẽ có hiệu suất cao hơn ở đây. Thực hiện THAM GIA TRÁI và lọc null từ bảng 2 chỉ là một cách dài dòng để sử dụng INNER THAM GIA
Pstr

Sai, điều này cung cấp hàng nhiều lần, giả sử bảng đã tham gia có thể được nối nhiều lần ... nếu không, hãy tham gia bên trong và bạn có thể dành cho mình vị trí.
Stefan Steiger

123

Thay vào đó, bạn sẽ muốn sử dụng cú pháp 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
Trong khi điều này sẽ hoạt động, nó chuyển đổi truy vấn không tương quan trong câu hỏi thành một truy vấn tương quan. Trừ khi trình tối ưu hóa truy vấn thông minh, điều này có thể mang lại cho bạn hiệu suất O (n ^ 2) :-(. Nhưng có lẽ tôi đang đánh giá thấp trình tối ưu hóa ...
sleske

1
Tôi sử dụng các cú pháp như thế này mọi lúc mà không có vấn đề. Trừ khi bạn đang sử dụng trình tối ưu hóa cũ hơn (6.5, 7, 8, v.v.) thì không nên có vấn đề với cú pháp này.
mrdenny

1
@sleske: EXISTS tốt hơn nhiều: xem ý kiến ​​của tôi trong câu trả lời của tôi. Và kiểm tra nó đầu tiên ,. @mrdenny: Lúc đầu tôi đọc sai câu trả lời của bạn, tôi cũng sẽ sử dụng EXISTS
gbn

6
Đây là hiệu quả nhất, +1. Xem bài viết này trong blog của tôi để so sánh hiệu suất: explainextended.com/2009/06/17/efficient-exists
Quassnoi

1
Ngay cả SQL 2000 cũng có thể xử lý hầu hết các truy vấn con tương quan mà không biến truy vấn thành O (n ^ 2). Có thể đã có một vấn đề trở lại vào ngày 6.5.
GilaMonster

14

CẢNH BÁO VỀ GIẢI PHÁP:

NHIỀU GIẢI PHÁP HIỆN HỮU SIVE GỬI ĐẦU RA SAU NẾU ROWS KHÔNG ĐỘC ĐÁO

Nếu bạn là người duy nhất tạo bảng, điều này có thể không liên quan, nhưng một số giải pháp sẽ cung cấp một số hàng đầu ra khác với mã được đề cập, khi một trong các bảng có thể không chứa các hàng duy nhất.

CẢNH BÁO VỀ TUYÊN BỐ VẤN ĐỀ:

Ở VỚI NHIỀU MÀU SẮC KHÔNG HIỆN TẠI, HÃY CẨN THẬN NHỮNG GÌ BẠN MUỐN

Khi tôi thấy một cột có hai cột, tôi có thể tưởng tượng nó có nghĩa là hai điều:

  1. Giá trị của cột a và cột b xuất hiện độc lập trong bảng khác
  2. Các giá trị của cột a và cột b xuất hiện trong bảng khác trên cùng một hàng

Kịch bản 1 khá tầm thường, chỉ cần sử dụng hai câu lệnh IN.

Theo hầu hết các câu trả lời hiện có, tôi xin cung cấp tổng quan về các phương pháp tiếp cận được đề cập và bổ sung cho Kịch bản 2 (và phán đoán ngắn gọn):

EXISTS (An toàn, được khuyến nghị cho SQL Server)

Như được cung cấp bởi @mrdenny, EXISTS nghe chính xác như những gì bạn đang tìm kiếm, đây là ví dụ của anh ấy:

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

LEFT SEMI THAM GIA (An toàn, được khuyến nghị cho các phương ngữ hỗ trợ nó)

Đây là một cách rất ngắn gọn để tham gia, nhưng thật không may, hầu hết các phương ngữ SQL, bao gồm cả máy chủ SQL hiện không hỗ trợ nó.

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

Nhiều câu lệnh IN (An toàn, nhưng hãy cẩn thận khi sao chép mã)

Như @cataclysm đã đề cập bằng cách sử dụng hai câu lệnh IN cũng có thể thực hiện thủ thuật này, có lẽ nó thậm chí sẽ vượt trội hơn các giải pháp khác. Tuy nhiên, điều bạn nên hết sức cẩn thận là sao chép mã. Nếu bạn muốn chọn từ một bảng khác hoặc thay đổi câu lệnh where, thì nguy cơ tăng cao là bạn tạo ra sự không nhất quán trong logic của mình.

Giải pháp cơ bản

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

Giải pháp không trùng lặp mã (Tôi tin rằng điều này không hoạt động trong các truy vấn SQL Server thông thường)

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)

INNER THAM GIA (về mặt kỹ thuật có thể được thực hiện an toàn, nhưng thường thì điều này không được thực hiện)

Lý do tại sao tôi không khuyên bạn nên sử dụng phép nối bên trong làm bộ lọc, bởi vì trong thực tế, mọi người thường để các bản sao ở bảng bên phải gây ra các bản sao ở bảng bên trái. Và sau đó để làm cho vấn đề tồi tệ hơn, đôi khi họ làm cho kết quả cuối cùng khác biệt trong khi bảng bên trái thực sự không cần phải là duy nhất (hoặc không phải là duy nhất trong các cột bạn chọn). Hơn nữa, nó cung cấp cho bạn cơ hội để thực sự chọn một cột không tồn tại trong bảng bên trái.

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

Những lỗi phổ biến nhất:

  1. Tham gia trực tiếp trên T2, không có truy vấn con an toàn. Kết quả là có nguy cơ trùng lặp)
  2. CHỌN * (Được sắp xếp để lấy các cột từ T2)
  3. CHỌN c (Không đảm bảo rằng cột của bạn đến và luôn luôn đến từ T1)
  4. Không có DISTINCT hoặc DISTINCT ở sai vị trí

CONCATENATION CỦA COLUMNS VỚI SEPARATOR (Không an toàn lắm, hiệu suất khủng khiếp)

Vấn đề chức năng là nếu bạn sử dụng một dấu phân tách có thể xảy ra trong một cột, sẽ rất khó để đảm bảo rằng kết quả chính xác 100%. Vấn đề kỹ thuật là phương pháp này thường phát sinh chuyển đổi loại và hoàn toàn bỏ qua các chỉ mục, dẫn đến hiệu suất có thể khủng khiếp. Mặc dù có những vấn đề này, tôi phải thừa nhận rằng đôi khi tôi vẫn sử dụng nó cho các truy vấn đặc biệt trên các bộ dữ liệu nhỏ.

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

Lưu ý rằng nếu các cột của bạn là số, một số phương ngữ SQL sẽ yêu cầu bạn chuyển chúng thành chuỗi trước. Tôi tin rằng máy chủ SQL sẽ làm điều này tự động.


Để kết thúc mọi thứ: Như thường lệ, có nhiều cách để thực hiện điều này trong SQL, sử dụng các lựa chọn an toàn sẽ tránh được các bất ngờ và giúp bạn tiết kiệm thời gian và tiêu đề trong thời gian dài.


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

Lưu ý:
Oracle bỏ qua các hàng trong đó một hoặc nhiều cột được chọn là NULL. Trong những trường hợp này, bạn có thể muốn sử dụng NVL -Funktion để ánh xạ NULL tới một giá trị đặc biệt (không nên có trong các giá trị);

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

2
postgres hỗ trợ where (colA,colB) in (... some list of tuples...)nhưng tôi không chắc những cơ sở dữ liệu nào khác cũng làm như vậy. Tôi muốn được biết.
Max Murphy

2
Cú pháp này cũng được hỗ trợ trong Oracle và DB2 / 400 (có lẽ là cả DB2). Chúc SQL Server hỗ trợ nó.
CrazyIvan1974 17/03/2016

DB2 hỗ trợ điều này.
Telmo Marques

Ngay cả SQLite cũng hỗ trợ nó.
Holger Jakobs

13

Một mệnh đề EXISTS đơn giản là sạch nhất

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
)

Nếu bạn có nhiều hàng trong tương quan thì THAM GIA cung cấp nhiều hàng trong đầu ra, vì vậy bạn cần có sự khác biệt. Mà thường làm cho EXISTS hiệu quả hơn.

Lưu ý SELECT *với lệnh JOIN cũng sẽ bao gồm các cột từ các bảng liên tiếp hạn chế


2

Tại sao nên sử dụng WHERE EXISTS hoặc DERIVED TABLES khi bạn chỉ có thể thực hiện một phép nối bên trong bình thường:

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

Nếu cặp (CM_PLAN_ID, Personal_ID) không phải là duy nhất trong bảng trạng thái, bạn có thể cần một CHỌN DISTINCT t. * Thay vào đó.


3
Và DISTINCT thường có nghĩa là một EXISTS hiệu quả hơn
gbn

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

1.Sử dụng với EXISTS[Thời gian truy vấn trung bình: 1,42 giây]

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.Sử dụng với hai dòng INĐiều khoản [Thời gian truy vấn trung bình: 0,37s]

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.Sử dụng với INNNER JOINmẫu [Thời gian truy vấn trung bình: 2.9s]

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

Vì vậy, tôi chọn tùy chọn thứ hai.


Cảnh báo cho độc giả trong tương lai: Phù hợp với câu hỏi, có thể bạn sẽ muốn sử dụng ANDcâu lệnh hơn là ORcâu lệnh.
Dennis Jaheruddin

@DennisJaheruddin .. Cảm ơn bạn đã nhận xét và giải thích chi tiết rất hay về câu trả lời của bạn. Bạn đã đúng, ORtuyên bố có thể tăng trùng lặp. Trong trường hợp của tôi, có có không bất kỳ hàng nào chứa cùng src_iddest_idtrong một hàng duy nhất. Vì vậy, sự trùng lặp sẽ không xảy ra trong trường hợp của tôi.
Cataclysm


-2

Nếu bạn muốn cho một bảng thì sử dụng truy vấn sau

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')

và dữ liệu bảng như sau

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

Sau đó xuất ra như sau

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

id in (1,2) and studentName in ('a','b')hoàn toàn không giống như (id, studentName) in ((1,'a'),(2,'b')). Chỉ cần nghĩ về một bản ghi có id = 2 và name = 'a'. Tất nhiên, nếu ID là duy nhất, thì hiệu ứng sẽ giảm đi, nhưng sau đó, nếu ID là duy nhất, chúng ta không cần phải lọc tên cả.
quetzalcoatl

-2

Chúng ta chỉ đơn giản có thể làm điều này.

   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

Ghép các cột lại với nhau trong một số hình thức là một "hack", nhưng khi sản phẩm không hỗ trợ bán kết nối cho nhiều hơn một cột, đôi khi bạn không có lựa chọn nào khác.

Ví dụ về nơi giải pháp nối trong / ngoài sẽ không hoạt động:

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

Khi các truy vấn không có tính chất tầm thường, đôi khi bạn không có quyền truy cập vào bảng cơ sở để thực hiện các phép nối bên trong / bên ngoài thông thường.

Nếu bạn sử dụng "hack" này, khi bạn kết hợp các trường, chỉ cần đảm bảo thêm đủ dấu phân cách ở giữa chúng để tránh giải thích sai, ví dụ: ColA + ":-:" + ColB


Câu trả lời này dường như không nhất quán (đề cập đến ghép và sau đó cung cấp một ví dụ khác). Ngoài ra, trên một ghi chú nhẹ hơn: Chúng tôi luôn có một sự lựa chọn ;-) Tôi đã thêm ví dụ ghép nối vào tổng quan của mình ở đây, với các chú thích có liên quan: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin

-3

Tôi thành lập dễ dàng hơn theo cách này

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 
) 

Hy vọng điều này giúp đỡ :)


9
Ouch, không có chỉ mục sử dụng ở đây làm cho chuỗi concat.
mrdenny

9
Tôi đã bỏ phiếu này vì nó nguy hiểm! Nếu CM_PLAN_ID = 45Individual_ID = 3sau đó kết quả kết quả trong 453- không thể phân biệt được với trường hợp CM_PLAN_ID = 4Individual_ID = 53... hỏi rắc rối tôi đã nghĩ
El Ronnoco

5
.. tất nhiên bạn có thể kết hợp với một char đặc biệt tùy ý 45_3, 45:3nhưng nó vẫn không phải là một giải pháp tốt và tất nhiên như @mrdenny nói rằng các chỉ mục sẽ không được sử dụng khi chuyển đổi đã diễn ra trên các cột.
El Ronnoco

1
Tôi cũng đã bỏ phiếu này, vì giải pháp này thực sự chỉ là một "hack" nhanh chóng. Nó chậm và như El Ronnoco nói, nó có thể dẫn đến lỗi.

-4

Cách đơn giản và sai sẽ được kết hợp hai cột bằng + hoặc ghép và tạo một cột.

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

Điều này sẽ được thực hiện khá chậm. Không thể được sử dụng trong lập trình nhưng nếu trong trường hợp bạn chỉ đang truy vấn để xác minh một cái gì đó có thể được sử dụng.


10
Thật vậy, và nó có thể dẫn đến lỗi, ví dụ: 'ab' + 'c' = 'a' + 'bc'
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.