SQL WHERE .. IN klauzula wiele kolumn

173

Muszę zaimplementować następujące zapytanie w 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
)

Ale klauzula WHERE..IN zezwala tylko na 1 kolumnę. Jak mogę porównać 2 lub więcej kolumn z innym wewnętrznym SELECT?

ala
źródło
Próbowałem przedstawić przegląd odpowiednich rozwiązań, z niezbędnymi ostrzeżeniami tutaj: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin

Odpowiedzi:

110

Możesz utworzyć tabelę pochodną z podzapytania i połączyć tabelę table1 z tą tabelą pochodną:

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
sleske
źródło
7
lub bardziej ogólnie SELECT * FROM table INNER JOIN otherTable ON (table.x = otherTable.a AND table.y = otherTable.b)
ala
4
A co z wieloma wierszami, które istniałyby, gdyby tabela 2 była elementem podrzędnym tabeli 1? I dlaczego LEWY DOŁĄCZ?
gbn
1
Tak, INNER JOIN byłoby tutaj bardziej wydajne. Wykonanie LEFT JOIN i odfiltrowanie wartości null z tabeli 2 to tylko
rozwlekły
Źle, to dostarcza wiersz wiele razy, zakładając, że połączona tabela może być połączona kilka razy ... w przeciwnym razie wykonaj sprzężenie wewnętrzne i możesz zaoszczędzić sobie gdzie.
Stefan Steiger
123

Zamiast tego będziesz chciał użyć składni 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)
mrdenny
źródło
5
Chociaż to zadziała, konwertuje nieskorelowane zapytanie w pytaniu na zapytanie skorelowane. O ile optymalizator zapytań nie jest sprytny, może to zapewnić wydajność O (n ^ 2) :-(. Ale może nie doceniam optymalizatora ...
sleske
1
Używam takich składni przez cały czas bez problemu. O ile nie używasz starszego optymalizatora (6.5, 7, 8 itd.), Nie powinien mieć problemu z tą składnią.
mrdenny,
1
@sleske: EXISTS jest o wiele lepsze: zobacz moje komentarze w mojej odpowiedzi. I najpierw przetestuj. @mrdenny: Na początku źle odczytałem twoją odpowiedź,
użyłbym
6
To jest najbardziej wydajne, +1. Zobacz ten artykuł na moim blogu, aby porównać wydajność: wyjaśnienie
xxtended.com/2009/06/17/efficient-exists
1
Nawet SQL 2000 mógł obsłużyć większość skorelowanych podzapytań bez przekształcania zapytania w O (n ^ 2). Może to być problem w wersji 6.5.
GilaMonster,
14

OSTRZEŻENIE DOTYCZĄCE ROZWIĄZAŃ:

WIELE ISTNIEJĄCYCH ROZWIĄZAŃ DAJE NIEPRAWIDŁOWE WYDAJNOŚĆ, JEŚLI WIERSZE NIE SĄ WYJĄTKOWE

Jeśli jesteś jedyną osobą tworzącą tabele, może to nie mieć znaczenia, ale kilka rozwiązań da inną liczbę wierszy wyjściowych z danego kodu, gdy jedna z tabel może nie zawierać unikalnych wierszy.

OSTRZEŻENIE DOTYCZĄCE PROBLEMU:

W PRZYPADKU WIELU KOLUMN NIE ISTNIEJE, UWAŻNIE PRZEMYŚL, CO CHCESZ

Kiedy widzę wejście z dwiema kolumnami, wyobrażam sobie, że oznacza to dwie rzeczy:

  1. Wartości z kolumny a i kolumny b pojawiają się niezależnie w drugiej tabeli
  2. Wartości z kolumny a i kolumny b pojawiają się w drugiej tabeli razem w tym samym wierszu

Scenariusz 1 jest dość trywialny, po prostu użyj dwóch instrukcji IN.

Zgodnie z większością istniejących odpowiedzi, niniejszym przedstawiam przegląd wspomnianych i dodatkowych podejść do scenariusza 2 (oraz krótką ocenę):

ISTNIEJE (bezpieczne, zalecane dla SQL Server)

Jak zapewnia @mrdenny, EXISTS brzmi dokładnie tak, jak szukasz, oto jego przykład:

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

LEFT SEMI JOIN (Bezpieczne, zalecane dla dialektów, które go obsługują)

Jest to bardzo zwięzły sposób dołączania, ale niestety większość dialektów SQL, w tym serwer SQL, obecnie go nie obsługuje.

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

Wiele instrukcji IN (bezpieczne, ale uważaj na powielanie kodu)

Jak wspomniał @cataclysm, użycie dwóch instrukcji IN również może załatwić sprawę, być może nawet przewyższy inne rozwiązania. Jednak należy być bardzo ostrożnym przy powielaniu kodu. Jeśli kiedykolwiek zechcesz wybrać z innej tabeli lub zmienić instrukcję where, istnieje zwiększone ryzyko, że stworzysz niespójności w swojej logice.

Podstawowe rozwiązanie

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

Rozwiązanie bez duplikacji kodu (uważam, że to nie działa w zwykłych zapytaniach 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)

WEWNĘTRZNE JOIN (technicznie można to zabezpieczyć, ale często się tego nie robi)

Powodem, dla którego nie polecam używania sprzężenia wewnętrznego jako filtru, jest to, że w praktyce ludzie często pozwalają, aby duplikaty w prawej tabeli powodowały duplikaty w lewej tabeli. Co gorsza, czasami sprawiają, że wynik końcowy jest inny, podczas gdy lewa tabela może nie być unikalna (lub nie jest unikalna w wybranych kolumnach). Co więcej, daje Ci możliwość faktycznego wybrania kolumny, której nie ma w lewej tabeli.

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

Najczęstsze błędy:

  1. Łączenie bezpośrednio na T2, bez bezpiecznego podzapytania. Prowadzi to do ryzyka powielania)
  2. SELECT * (gwarantowane pobranie kolumn z T2)
  3. SELECT c (nie gwarantuje, że Twoja kolumna pochodzi i zawsze będzie pochodzić z T1)
  4. Żadnego DISTINCT lub DISTINCT w niewłaściwym miejscu

ZŁĄCZENIE KOLUMN Z SEPARATOREM (Niezbyt bezpieczne, straszne wykonanie)

Problem funkcjonalny polega na tym, że jeśli użyjesz separatora, który może wystąpić w kolumnie, trudno jest upewnić się, że wynik jest w 100% dokładny. Problem techniczny polega na tym, że ta metoda często powoduje konwersję typów i całkowicie ignoruje indeksy, co skutkuje prawdopodobnie okropną wydajnością. Mimo tych problemów muszę przyznać, że czasami nadal używam go do zapytań ad-hoc na małych zbiorach danych.

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

Zwróć uwagę, że jeśli kolumny są numeryczne, niektóre dialekty SQL będą wymagały najpierw rzutowania ich na łańcuchy. Uważam, że serwer SQL zrobi to automatycznie.


Podsumowując: jak zwykle w SQL jest wiele sposobów, aby to zrobić, używanie bezpiecznych wyborów pozwoli uniknąć niespodzianek i zaoszczędzić czas i na dłuższą metę.

Dennis Jaheruddin
źródło
13
select * from tab1 where (col1,col2) in (select col1,col2 from tab2)

Uwaga:
Oracle ignoruje wiersze, w których co najmniej jedna z wybranych kolumn ma wartość NULL. W takich przypadkach prawdopodobnie będziesz chciał użyć NVL -Funktion do odwzorowania NULL na specjalną wartość (która nie powinna znajdować się w wartościach);

select * from tab1
where (col1, NVL(col2, '---') in (select col1, NVL(col2, '---') from tab2)
tommes-pommes
źródło
2
Postgres obsługuje, where (colA,colB) in (... some list of tuples...)ale nie jestem pewien, jakie inne bazy danych robią to samo. Chciałbym wiedzieć.
Max Murphy
2
Ta składnia jest również obsługiwana w Oracle i DB2 / 400 (prawdopodobnie także w DB2). Wish SQL Server to obsługiwał.
CrazyIvan1974
DB2 to obsługuje.
Telmo Marques
Nawet SQLite to obsługuje.
Holger Jakobs
13

Prosta klauzula EXISTS jest najczystsza

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
)

Jeśli masz wiele wierszy w korelacji, wówczas JOIN daje wiele wierszy w wyniku, więc potrzebujesz różnych. Co zwykle sprawia, że ​​ISTNIEJE bardziej wydajne.

Uwaga SELECT *z JOIN obejmowałaby również kolumny z tabel ograniczających wiersze

gbn
źródło
2

Po co używać GDZIE ISTNIEJE lub POCHODZONE TABELE, skoro można po prostu wykonać zwykłe sprzężenie wewnętrzne:

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

Jeśli para (CM_PLAN_ID, Individual_ID) nie jest unikalna w tabeli statusów, możesz potrzebować zamiast tego SELECT DISTINCT t. *.

BradC
źródło
3
A DISTINCT zwykle oznacza, że ​​EXISTS jest bardziej wydajne
gbn
0
Postgres SQL  : version 9.6
Total records on tables : mjr_agent = 145, mjr_transaction_item = 91800

1.Używanie z EXISTS[Średni czas zapytania: 1,42 s]

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. Używanie z dwoma wierszami INKlauzula [średni czas zapytania: 0,37 s]

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.Używanie z INNNER JOINwzorcem [średni czas zapytania: 2,9 s]

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

Więc wybrałem drugą opcję.

Kataklizm
źródło
Ostrzeżenie dla przyszłych czytelników: zgodnie z pytaniem, prawdopodobnie będziesz chciał używać ANDraczej ORstwierdzeń niż stwierdzeń.
Dennis Jaheruddin
@DennisJaheruddin .. Dziękuję za komentarz i bardzo ładne szczegółowe wyjaśnienia Twojej odpowiedzi. Masz rację, ORstwierdzenie prawdopodobnie wywoła powielenia. W moim przypadku nie ma żadnych wierszy zawierających to samo src_idi dest_idw jednym wierszu. Tak więc w moim przypadku nie dojdzie do duplikacji.
Cataclysm
-2

Jeśli chcesz dla jednej tabeli, użyj następującego zapytania

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

i dane w tabeli, jak następuje

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

Następnie wypisz w następujący sposób

id|name|adde|city
1   a   ad  ca
2   b   bd  bd
Somnath Kadam
źródło
id in (1,2) and studentName in ('a','b')nie jest tym samym, co (id, studentName) in ((1,'a'),(2,'b')). Wystarczy pomyśleć o rekordzie mającym id = 2 i name = 'a'. Oczywiście, jeśli identyfikator jest unikalny, efekt jest zmniejszony, ale jeśli identyfikator jest unikalny, nie musimy w ogóle filtrować nazw.
quetzalcoatl
-2

Możemy to po prostu zrobić.

   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
Rpant
źródło
-2

Łączenie kolumn ze sobą w jakiejś formie to „hack”, ale jeśli produkt nie obsługuje łączeń połowicznych dla więcej niż jednej kolumny, czasami nie masz wyboru.

Przykład sytuacji, w której połączenie wewnętrzne / zewnętrzne nie zadziała:

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

Gdy zapytania nie są z natury trywialne, czasami nie masz dostępu do tabeli podstawowej ustawionej na wykonywanie zwykłych sprzężeń wewnętrznych / zewnętrznych.

Jeśli korzystasz z tego „hackowania”, podczas łączenia pól po prostu upewnij się, że dodałeś wystarczająco dużo separatora między nimi, aby uniknąć błędnych interpretacji, np. ColA + ":-:" + ColB

John K.
źródło
Ta odpowiedź wydaje się niespójna (wspomina o konkatenacji, a następnie podaje inny przykład). A propos: zawsze mamy wybór ;-)
Dodałem
-3

W ten sposób założyłem łatwiej

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 
) 

Mam nadzieję, że to pomoże :)

Lisandro Acosta
źródło
9
Uuuuuuu, nie używaj tutaj indeksu do konkatowania ciągów.
mrdenny
9
Odrzuciłem to głosowanie, ponieważ jest to po prostu niebezpieczne! Jeżeli CM_PLAN_ID = 45i Individual_ID = 3wtedy wyniki konkatenacji we 453- co jest nie do odróżnienia od przypadku CM_PLAN_ID = 4i Individual_ID = 53... kłopoty bym myślał
El Ronnoco
5
.. oczywiście można łączyć z dowolnym znakiem specjalnym, np. 45_3lub, 45:3ale to wciąż nie jest dobre rozwiązanie i oczywiście, jak mówi @mrdenny, indeksy nie będą używane teraz, gdy dokonano transformacji kolumn.
El Ronnoco,
1
Głosowałem również na to odrzucenie, ponieważ to rozwiązanie jest tylko szybkim "hackowaniem". Jest powolny i, jak powiedział El Ronnoco, może prowadzić do błędów.
-4

Prostym i złym sposobem byłoby połączenie dwóch kolumn za pomocą + lub konkatenacja i utworzenie jednej kolumny.

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

To byłoby całkiem powolne. Nie może być używany w programowaniu, ale jeśli pytasz tylko o weryfikację, może być użyty.

Vijay
źródło
10
Rzeczywiście, i może to prowadzić do błędów, ponieważ np. 'Ab' + 'c' = 'a' + 'bc'