SQL WHERE .. clause IN plusieurs colonnes

173

Je dois implémenter la requête suivante dans 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
)

Mais la clause WHERE..IN n'autorise qu'une seule colonne. Comment puis-je comparer 2 colonnes ou plus avec un autre SELECT interne?

ala
la source
J'ai essayé de donner un aperçu des solutions pertinentes, avec les précautions nécessaires ici: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin

Réponses:

110

Vous pouvez créer une table dérivée à partir de la sous-requête et joindre table1 à cette table dérivée:

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
la source
7
ou plus généralement SELECT * FROM table INNER JOIN otherTable ON (table.x = otherTable.a AND table.y = otherTable.b)
ala
4
Qu'en est-il des lignes multiples qui existeraient si la table 2 est un enfant de la table 1? Et pourquoi LEFT JOIN?
gbn
1
Ouais, INNER JOIN serait plus performant ici. Faire un LEFT JOIN et filtrer les valeurs nulles de la table 2 est juste une façon verbeuse d'utiliser un INNER JOIN
Pstr
Faux, cela livre la ligne plusieurs fois, en supposant que la table jointe puisse être jointe plusieurs fois ... sinon, faites une jointure interne et vous pouvez vous épargner où.
Stefan Steiger
123

Vous voudrez plutôt utiliser la syntaxe 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
la source
5
Bien que cela fonctionne, cela convertit la requête non corrélée de la question en requête corrélée. À moins que l'optimiseur de requête ne soit intelligent, cela pourrait vous donner des performances O (n ^ 2) :-(. Mais peut-être que je sous-estime l'optimiseur ...
sleske
1
J'utilise des syntaxes comme celle-ci tout le temps sans problème. Sauf si vous utilisez un optimiseur plus ancien (6.5, 7, 8, etc.), il ne devrait pas avoir de problème avec cette syntaxe.
mrdenny
1
@sleske: EXISTS est de loin meilleur: voir mes commentaires dans ma réponse. Et testez-le d'abord ,. @mrdenny: J'ai mal lu votre réponse au début, j'utiliserais aussi EXISTS
gbn
6
C'est le plus efficace, +1. Voir cet article dans mon blog pour une comparaison des performances: expliquezextended.com/2009/06/17/effic-exists
Quassnoi
1
Même SQL 2000 pourrait gérer la plupart des sous-requêtes corrélées sans transformer la requête en un O (n ^ 2). Cela aurait pu être un problème avec la version 6.5.
GilaMonster
14

AVERTISSEMENT CONCERNANT LES SOLUTIONS:

DE NOMBREUSES SOLUTIONS EXISTANTES DONNERONT UNE FAUTE SORTIE SI LES RANGÉES NE SONT PAS UNIQUES

Si vous êtes la seule personne à créer des tables, cela peut ne pas être pertinent, mais plusieurs solutions donneront un nombre de lignes de sortie différent du code en question, lorsque l'une des tables peut ne pas contenir de lignes uniques.

AVERTISSEMENT CONCERNANT L'ÉNONCÉ DU PROBLÈME:

IN AVEC PLUSIEURS COLONNES N'EXISTE PAS, PENSEZ ATTENTIVEMENT CE QUE VOUS VOULEZ

Quand je vois un in avec deux colonnes, je peux l'imaginer pour signifier deux choses:

  1. La valeur de la colonne a et de la colonne b apparaît dans l'autre tableau indépendamment
  2. Les valeurs de la colonne a et de la colonne b apparaissent dans l'autre tableau ensemble sur la même ligne

Le scénario 1 est assez trivial, utilisez simplement deux instructions IN.

Conformément à la plupart des réponses existantes, je donne ici un aperçu des approches mentionnées et supplémentaires pour le scénario 2 (et un bref jugement):

EXISTS (sûr, recommandé pour SQL Server)

Tel que fourni par @mrdenny, EXISTS sonne exactement comme ce que vous recherchez, voici son exemple:

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

LEFT SEMI JOIN (Safe, recommandé pour les dialectes qui le supportent)

C'est une manière très concise de rejoindre, mais malheureusement, la plupart des dialectes SQL, y compris le serveur SQL, ne le prennent pas actuellement en charge.

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

Plusieurs instructions IN (sûr, mais attention à la duplication de code)

Comme mentionné par @cataclysm, l'utilisation de deux instructions IN peut également faire l'affaire, peut-être même qu'elle surpassera les autres solutions. Cependant, vous devez faire très attention à la duplication de code. Si vous souhaitez effectuer une sélection dans une autre table ou modifier l'instruction where, vous risquez davantage de créer des incohérences dans votre logique.

Solution basique

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

Solution sans duplication de code (je pense que cela ne fonctionne pas dans les requêtes SQL Server régulières)

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 (techniquement, cela peut être sécurisé, mais souvent cela n'est pas fait)

La raison pour laquelle je ne recommande pas d'utiliser une jointure interne comme filtre, c'est qu'en pratique, les gens laissent souvent les doublons dans la table de droite provoquer des doublons dans la table de gauche. Et puis, pour aggraver les choses, ils rendent parfois le résultat final distinct alors que la table de gauche n'a peut-être pas besoin d'être unique (ou pas unique dans les colonnes que vous sélectionnez). De plus, cela vous donne la possibilité de sélectionner une colonne qui n'existe pas dans le tableau de gauche.

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

Erreurs les plus courantes:

  1. Rejoindre directement sur T2, sans sous-requête sûre. Entraînant un risque de duplication)
  2. SELECT * (garanti pour obtenir les colonnes de T2)
  3. SELECT c (ne garantit pas que votre colonne vient et proviendra toujours de T1)
  4. Pas de DISTINCT ou DISTINCT au mauvais endroit

CONCATENATION DE COLONNES AVEC SEPARATEUR (Pas très sûr, performances horribles)

Le problème fonctionnel est que si vous utilisez un séparateur qui pourrait apparaître dans une colonne, il devient difficile de s'assurer que le résultat est précis à 100%. Le problème technique est que cette méthode entraîne souvent des conversions de type et ignore complètement les index, ce qui entraîne des performances potentiellement horribles. Malgré ces problèmes, je dois admettre que je l'utilise encore parfois pour des requêtes ad hoc sur de petits ensembles de données.

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

Notez que si vos colonnes sont numériques, certains dialectes SQL vous demanderont d'abord de les convertir en chaînes. Je crois que le serveur SQL le fera automatiquement.


Pour résumer les choses: comme d'habitude, il existe de nombreuses façons de faire cela en SQL, utiliser des choix sûrs évitera les surprises et vous fera gagner du temps et de la tête à long terme.

Dennis Jaheruddin
la source
13
select * from tab1 where (col1,col2) in (select col1,col2 from tab2)

Remarque:
Oracle ignore les lignes où une ou plusieurs des colonnes sélectionnées sont NULL. Dans ces cas, vous voudrez probablement utiliser NVL -Funktion pour mapper NULL à une valeur spéciale (qui ne devrait pas être dans les valeurs);

select * from tab1
where (col1, NVL(col2, '---') in (select col1, NVL(col2, '---') from tab2)
tommes-pommes
la source
2
postgres prend en charge where (colA,colB) in (... some list of tuples...)mais je ne suis pas sûr de ce que les autres bases de données font de même. Je serais intéressé de savoir.
Max Murphy
2
Cette syntaxe est également prise en charge dans Oracle et DB2 / 400 (probablement également DB2). Wish SQL Server l'a pris en charge.
CrazyIvan1974
DB2 prend en charge cela.
Telmo Marques
Même SQLite le prend en charge.
Holger Jakobs
13

Une simple clause EXISTS est la plus propre

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
)

Si vous avez plusieurs lignes dans la corrélation, un JOIN donne plusieurs lignes dans la sortie, vous aurez donc besoin de distinctes. Ce qui rend généralement les EXISTS plus efficaces.

La note SELECT *avec un JOIN inclurait également les colonnes des tables de limitation de ligne

gbn
la source
2

Pourquoi utiliser WHERE EXISTS ou DERIVED TABLES lorsque vous pouvez simplement faire une jointure interne normale:

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

Si la paire de (CM_PLAN_ID, Individual_ID) n'est pas unique dans la table d'état, vous aurez peut-être besoin d'un SELECT DISTINCT t. * À la place.

BradC
la source
3
Et le DISTINCT signifie généralement qu'un EXISTS est plus efficace
gbn
0
Postgres SQL  : version 9.6
Total records on tables : mjr_agent = 145, mjr_transaction_item = 91800

1.Utilisation avec EXISTS[Temps de requête moyen: 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. Utilisation de la clause avec deux lignes IN[Temps de requête moyen: 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.Utilisation avec un INNNER JOINmodèle [Temps de requête moyen: 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

Alors, j'ai choisi la deuxième option.

Cataclysme
la source
Avertissement pour les futurs lecteurs: conformément à la question, vous voudrez probablement utiliser des ANDdéclarations plutôt que des ORdéclarations.
Dennis Jaheruddin
@DennisJaheruddin .. Merci pour ton commentaire et de très belles explications détaillées de ta réponse. Vous avez raison, la ORdéclaration soulève probablement des doublons. Dans mon cas, il n'y a aucune ligne qui contient la même chose src_idet dest_idsur une seule ligne. Donc, les duplications ne se produiront pas dans mon cas.
Cataclysm
-2

Si vous voulez une table, utilisez la requête suivante

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

et les données du tableau comme suit

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

Puis sortie comme suit

id|name|adde|city
1   a   ad  ca
2   b   bd  bd
Somnath Kadam
la source
id in (1,2) and studentName in ('a','b')est totalement différent de (id, studentName) in ((1,'a'),(2,'b')). Pensez simplement à un enregistrement ayant id = 2 et name = 'a'. Bien sûr, si l'ID est unique, l'effet est diminué, mais ensuite, si l'ID est unique, nous n'avons pas du tout besoin de filtrer les noms.
quetzalcoatl
-2

Nous pouvons simplement le faire.

   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
la source
-2

Concaténer les colonnes ensemble sous une forme ou une autre est un "hack", mais lorsque le produit ne prend pas en charge les semi-jointures pour plus d'une colonne, vous n'avez parfois pas le choix.

Exemple où la solution de jointure interne / externe ne fonctionnerait pas:

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

Lorsque les requêtes ne sont pas de nature triviale, vous n'avez parfois pas accès à l'ensemble de table de base pour effectuer des jointures internes / externes régulières.

Si vous utilisez ce "hack", lorsque vous combinez des champs, assurez-vous simplement d'ajouter suffisamment de délimiteur entre eux pour éviter les erreurs d'interprétation, par exemple ColA + ":-:" + ColB

John K
la source
Cette réponse semble incohérente (mentionne la concaténation et donne ensuite un exemple différent). Aussi, sur une note plus légère: nous avons toujours le choix ;-) J'ai ajouté l'exemple de concaténation à mon aperçu ici, avec les notes de bas de page pertinentes: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin
-3

J'ai fondé plus facilement de cette façon

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 
) 

J'espère que cette aide :)

Lisandro Acosta
la source
9
Aïe, aucun index n'est utilisé ici pour la chaîne concat.
mrdenny
9
J'ai voté contre cela car c'est tout simplement dangereux! Si CM_PLAN_ID = 45et Individual_ID = 3ensuite la concaténation aboutit 453- ce qui est impossible à distinguer du cas où CM_PLAN_ID = 4et Individual_ID = 53... demander des ennuis, j'aurais pensé
El Ronnoco
5
.. Bien sûr, vous pouvez concaténer avec un caractère spécial arbitraire, par exemple 45_3ou 45:3mais ce n'est toujours pas une bonne solution et bien sûr comme @mrdenny dit que les index ne seront plus utilisés maintenant qu'une transformation a eu lieu sur les colonnes.
El Ronnoco
1
J'ai également voté contre cette solution, car cette solution est vraiment un simple "hack" rapide. C'est lent et comme l'a dit El Ronnoco, cela peut conduire à des bugs.
-4

Une manière simple et incorrecte serait de combiner deux colonnes en utilisant + ou de concaténer et de créer une colonne.

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

Ce serait bien sûr assez lent. Ne peut pas être utilisé dans la programmation, mais si dans le cas où vous demandez juste pour vérifier quelque chose peut être utilisé.

Vijay
la source
10
En effet, et cela peut conduire à des erreurs, puisque par exemple 'ab' + 'c' = 'a' + 'bc'