Clausola SQL WHERE .. IN più colonne


173

Devo implementare la seguente query in 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
)

Ma la clausola WHERE..IN consente solo 1 colonna. Come posso confrontare 2 o più colonne con un altro SELECT interno?


Ho cercato di fornire una panoramica delle soluzioni rilevanti, con le cautele neccesary qui: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin

Risposte:


110

È possibile creare una tabella derivata dalla sottoquery e unire table1 a questa tabella derivata:

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
o più in generale SELEZIONA * DA tabella INNER JOIN otherTable ON (table.x = otherTable.a AND table.y = otherTable.b)
ala

4
Che dire delle righe multiple che esisterebbero se la tabella 2 fosse una figlia della tabella 1? E perché SINISTRA ISCRIVITI?
gbn,

1
Sì, INNER JOIN sarebbe più performante qui. Fare un JOIN SINISTRA e filtrare i null dalla tabella 2 è solo un modo dettagliato per usare un JOIN INNER
Pstr

Sbagliato, questo consegna la riga più volte, supponendo che la tabella unita possa essere unita più volte ... altrimenti, fai un join interno e puoi risparmiare te stesso dove.
Stefan Steiger,

123

In alternativa, ti consigliamo di utilizzare la sintassi 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
Mentre ciò funzionerebbe, converte la query non correlata nella domanda in una query correlata. A meno che l'ottimizzatore della query non sia intelligente, questo potrebbe darti prestazioni O (n ^ 2) :-(. Ma forse sto sottovalutando l'ottimizzatore ...
sleske,

1
Uso sintassi come questa per tutto il tempo senza problemi. A meno che tu non stia utilizzando un vecchio ottimizzatore (6.5, 7, 8, ecc.) Non dovrebbe avere problemi con questa sintassi.
mrdenny,

1
@sleske: EXISTS è di gran lunga migliore: vedi i miei commenti nella mia risposta. E prima provalo. @mrdenny: all'inizio ho letto male la tua risposta, userei anche EXISTS
gbn

6
Questo è più efficiente, +1. Si veda questo articolo nel mio blog per il confronto delle prestazioni: explainextended.com/2009/06/17/efficient-exists
Quassnoi

1
Perfino SQL 2000 poteva gestire la maggior parte delle sottoquery correlate senza trasformare la query in una O (n ^ 2). Potrebbe essere stato un problema a 6.5.
GilaMonster,

14

ATTENZIONE SULLE SOLUZIONI:

MOLTE SOLUZIONI ESISTENTI DANNO L'USCITA SBAGLIATA SE LE FILE NON SONO UNICHE

Se sei l'unica persona che crea tabelle, questo potrebbe non essere pertinente, ma diverse soluzioni forniranno un numero diverso di righe di output rispetto al codice in questione, quando una delle tabelle potrebbe non contenere righe univoche.

AVVERTENZA SULLA DICHIARAZIONE DEL PROBLEMA:

IN CON PIÙ COLONNE NON ESISTE, PENSA CON ATTENZIONE A COSA VUOI

Quando vedo un in con due colonne, posso immaginare che significhi due cose:

  1. Il valore della colonna a e della colonna b viene visualizzato nell'altra tabella in modo indipendente
  2. I valori della colonna a e della colonna b compaiono insieme nell'altra tabella sulla stessa riga

Lo scenario 1 è abbastanza banale, basta usare due istruzioni IN.

In linea con la maggior parte delle risposte esistenti, fornisco una panoramica degli approcci citati e aggiuntivi per lo Scenario 2 (e un breve giudizio):

EXISTS (sicuro, consigliato per SQL Server)

Come fornito da @mrdenny, EXISTS suona esattamente come quello che stai cercando, ecco il suo esempio:

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

LEFT SEMI JOIN (Sicuro, consigliato per i dialetti che lo supportano)

Questo è un modo molto conciso per unirsi, ma sfortunatamente la maggior parte dei dialetti SQL, incluso SQL Server, al momento non lo supporta.

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

Istruzioni multiple IN (sicure, ma attenzione alla duplicazione del codice)

Come accennato da @cataclysm, usare due istruzioni IN può fare anche il trucco, forse supererà anche le altre soluzioni. Tuttavia, ciò di cui dovresti fare molta attenzione è la duplicazione del codice. Se mai si desidera selezionare da una tabella diversa o modificare l'istruzione where, è un rischio maggiore che si creino incoerenze nella logica.

Soluzione di base

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

Soluzione senza duplicazione del codice (credo che questo non funzioni nelle normali query di 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)

INNER JOIN (tecnicamente può essere reso sicuro, ma spesso non lo si fa)

Il motivo per cui non consiglio di usare un join interno come filtro, è perché in pratica le persone spesso lasciano duplicati nella tabella di destra causando duplicati nella tabella di sinistra. E poi, a peggiorare le cose, a volte rendono distinto il risultato finale mentre la tabella di sinistra potrebbe non aver bisogno di essere unica (o non unica nelle colonne selezionate). Inoltre ti dà la possibilità di selezionare effettivamente una colonna che non esiste nella tabella di sinistra.

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

Errori più comuni:

  1. Partecipare direttamente su T2, senza una subquery sicura. Con conseguente rischio di duplicazione)
  2. SELEZIONA * (Garantito per ottenere colonne da T2)
  3. SELEZIONA c (Non garantisce che la tua colonna arrivi e provenga sempre da T1)
  4. Nessun DISTINCT o DISTINCT nel posto sbagliato

CONCATENAZIONE DI COLONNE CON SEPARATORE (Prestazioni non molto sicure, orribili)

Il problema funzionale è che se si utilizza un separatore che potrebbe verificarsi in una colonna, diventa difficile garantire che il risultato sia accurato al 100%. Il problema tecnico è che questo metodo spesso comporta conversioni di tipo e ignora completamente gli indici, con conseguenti prestazioni orribili. Nonostante questi problemi, devo ammettere che a volte lo uso ancora per query ad hoc su piccoli set di dati.

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

Nota che se le tue colonne sono numeriche, alcuni dialetti SQL richiederanno di lanciarle prima nelle stringhe. Credo che SQL Server lo farà automaticamente.


Per concludere: come al solito ci sono molti modi per farlo in SQL, l'uso di scelte sicure eviterà sorprese e ti farà risparmiare tempo e titoli a lungo termine.


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

Nota:
Oracle ignora le righe in cui una o più colonne selezionate è NULL. In questi casi, probabilmente si desidera utilizzare NVL -Funktion per mappare NULL su un valore speciale (che non dovrebbe essere compreso nei valori);

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

2
Postgres supporta where (colA,colB) in (... some list of tuples...)ma non sono sicuro di quali altri database facciano lo stesso. Sarei interessato a sapere.
Max Murphy,

2
Questa sintassi è supportata anche in Oracle e DB2 / 400 (probabilmente anche DB2). Vorrei che SQL Server lo supportasse.
CrazyIvan1974,

DB2 supporta questo.
Telmo Marques,

Anche SQLite lo supporta.
Holger Jakobs,

13

Una semplice clausola EXISTS è la più pulita

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
)

Se nella correlazione sono presenti più righe, un JOIN fornisce più righe nell'output, quindi è necessario distinguerle. Il che di solito rende gli EXISTS più efficienti.

Nota SELECT *con un JOIN includerebbe anche le colonne dalle tabelle di limitazione delle righe


2

Perché usare WHERE EXISTS o DERIVED TABLES quando puoi semplicemente fare un normale join interno:

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

Se la coppia di (CM_PLAN_ID, Individual_ID) non è univoca nella tabella di stato, potrebbe essere necessario invece SELECT DISTINCT t. *.


3
E il DISTINCT di solito significa che un ESISTO è più efficiente
gbn

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

1.Utilizzo con EXISTS[Tempo medio query: 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.Utilizzo con due righe INClausola [Tempo medio query: 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.Utilizzo con INNNER JOINmodello [Tempo medio query: 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

Quindi, ho scelto la seconda opzione.


Avvertenza per i futuri lettori: in linea con la domanda, probabilmente vorrai usare le ANDdichiarazioni piuttosto che le ORdichiarazioni.
Dennis Jaheruddin,

@DennisJaheruddin .. Grazie per il tuo commento e le spiegazioni di dettaglio molto belle della tua risposta. Hai ragione, l' ORaffermazione probabilmente genera duplicazioni. Nel mio caso, non ci sono righe che contengono lo stesso src_ide dest_idin una singola riga. Quindi, nel mio caso non succederanno duplicazioni.
Cataclisma,


-2

Se si desidera una tabella, utilizzare la seguente query

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

e i dati della tabella come segue

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

Quindi emettere come segue

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

id in (1,2) and studentName in ('a','b')non è assolutamente lo stesso di (id, studentName) in ((1,'a'),(2,'b')). Basti pensare a un record con id = 2 e name = 'a'. Naturalmente, se ID è univoco, l'effetto è ridotto, ma quindi, se ID è univoco, non è necessario filtrare i nomi.
quetzalcoatl,

-2

Possiamo semplicemente farlo.

   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

Concatenare le colonne insieme in qualche forma è un "hack", ma quando il prodotto non supporta i semi-join per più di una colonna, a volte non hai scelta.

Esempio di dove la soluzione di join interno / esterno non funzionerebbe:

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

Quando le query non sono banali in natura a volte non si ha accesso al set di tabelle di base per eseguire regolari join interni / esterni.

Se usi questo "hack", quando combini i campi assicurati solo di aggiungere abbastanza delimitatori tra loro per evitare errori di interpretazione, ad es. ColA + ":-:" + ColB


Questa risposta sembra incoerente (menziona la concatenazione e quindi fornisce un esempio diverso). Inoltre, su una nota più leggera: Abbiamo sempre una scelta ;-) ho aggiunto l'esempio di concatenazione alla mia panoramica qui, con le note rilevanti: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin

-3

Ho trovato più facile in questo modo

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 
) 

Spero che questo aiuto :)


9
Inoltre, nessun indice utilizzato qui fa per la stringa concat.
mrdenny,

9
L'ho votato perché è assolutamente pericoloso! Se CM_PLAN_ID = 45e Individual_ID = 3poi la concatenazione si traduce in 453- il che è indistinguibile dal caso in cui CM_PLAN_ID = 4e Individual_ID = 53... chiedere guai avrei pensato
El Ronnoco

5
... ovviamente potresti concatenarti con un carattere speciale arbitrario, ad esempio 45_3o, 45:3ma non è ancora una buona soluzione e, naturalmente, come dice @mrdenny, gli indici non verranno utilizzati ora che si è verificata una trasformazione sulle colonne.
El Ronnoco,

1
Ho anche votato al ribasso, poiché questa soluzione è davvero solo un "hack" rapido. È lento e, come diceva El Ronnoco, può portare a bug.

-4

Un modo semplice e sbagliato sarebbe combinare due colonne usando + o concatenare e creare una colonna.

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

Questo sarebbe molto lento. Non può essere utilizzato in programmazione, ma nel caso in cui si stia solo eseguendo una query per verificare qualcosa può essere utilizzato.


10
In effetti, e può portare a errori, dal momento che ad esempio 'ab' + 'c' = 'a' + 'bc'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.