Cláusula SQL WHERE .. IN várias colunas

173

Preciso implementar a seguinte consulta no 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
)

Mas a cláusula WHERE..IN permite apenas 1 coluna. Como posso comparar 2 ou mais colunas com outro SELECT interno?

ala
fonte
Tentei fornecer uma visão geral das soluções relevantes, com as precauções neccesary aqui: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin

Respostas:

110

Você pode criar uma tabela derivada da subconsulta e associar table1 a esta tabela derivada:

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
fonte
7
ou mais geralmente SELECT * FROM table INNER JOIN otherTable ON (table.x = otherTable.a AND table.y = otherTable.b)
ala
4
E as várias linhas que existiriam se a tabela 2 fosse filha da tabela 1? E por que deixar JOIN?
gbn 16/07/09
1
Sim, INNER JOIN seria mais eficiente aqui. Fazer um LEFT JOIN e filtrar os nulos da tabela 2 é apenas uma maneira detalhada de usar um INNER JOIN
Pstr
Errado, isso fornece a linha várias vezes, supondo que a tabela unida possa ser unida várias vezes ... caso contrário, faça uma junção interna e você poderá se poupar do local.
Stefan Steiger
123

Você desejará usar a sintaxe 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
fonte
5
Enquanto isso funcionaria, ele converte a consulta não correlacionada na pergunta em uma consulta correlacionada. A menos que o otimizador de consulta é inteligente, isso pode dar-lhe O (n ^ 2) desempenho :-( Mas talvez eu esteja subestimando o otimizador ....
sleske
1
Eu uso sintaxes como essa o tempo todo, sem problemas. A menos que você esteja usando um otimizador mais antigo (6.5, 7, 8 etc.), ele não deve ter problemas com essa sintaxe.
Mrdenny
1
@lesleske: EXISTS é de longe melhor: veja meus comentários na minha resposta. E teste primeiro. @mrdenny: Eu descaracterizou a sua resposta à primeira, eu uso também existe
GBN
6
Isso é mais eficiente, +1. Veja este artigo no meu blog para comparação de desempenho: explainextended.com/2009/06/17/efficient-exists
Quassnoi
1
Mesmo o SQL 2000 poderia manipular a maioria das subconsultas correlacionadas sem transformar a consulta em um O (n ^ 2). Pode ter sido um problema no 6.5.
GilaMonster 17/07/2009
14

AVISO SOBRE SOLUÇÕES:

MUITAS SOLUÇÕES EXISTENTES DARÃO SAÍDA ERRADA SE AS LINHAS NÃO SÃO ÚNICAS

Se você é a única pessoa que cria tabelas, isso pode não ser relevante, mas várias soluções fornecerão um número diferente de linhas de saída do código em questão, quando uma das tabelas não puder conter linhas exclusivas.

AVISO SOBRE DECLARAÇÃO DE PROBLEMA:

EM VÁRIAS COLUNAS NÃO EXISTE, PENSE CUIDADOSAMENTE O QUE VOCÊ QUER

Quando vejo uma entrada com duas colunas, consigo imaginar duas coisas:

  1. O valor da coluna ae coluna b aparecem na outra tabela independentemente
  2. Os valores das colunas ae coluna aparecem na outra tabela juntos na mesma linha

O cenário 1 é bastante trivial, basta usar duas instruções IN.

De acordo com a maioria das respostas existentes, forneço aqui uma visão geral das abordagens mencionadas e adicionais para o Cenário 2 (e um breve julgamento):

EXISTS (Seguro, recomendado para SQL Server)

Conforme fornecido por @mrdenny, EXISTS soa exatamente como o que você está procurando, eis o exemplo dele:

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

ESQUERDA SEMI JOIN (Seguro, recomendado para dialetos que o suportam)

Essa é uma maneira muito concisa de ingressar, mas infelizmente a maioria dos dialetos SQL, incluindo o SQL Server, atualmente não o suporta.

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

Várias instruções IN (seguras, mas cuidado com a duplicação de código)

Como mencionado pelo @cataclysm, usar duas instruções IN também pode ajudar, talvez até supere as outras soluções. No entanto, você deve ter muito cuidado com a duplicação de código. Se você desejar selecionar uma tabela diferente ou alterar a instrução where, é um risco aumentado que você crie inconsistências em sua lógica.

Solução básica

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

Solução sem duplicação de código (acredito que isso não funcione em consultas regulares do 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, isso pode ser feito com segurança, mas muitas vezes isso não é feito)

A razão pela qual eu não recomendo o uso de uma junção interna como filtro, é porque, na prática, as pessoas geralmente deixam duplicatas na tabela correta, causando duplicatas na tabela esquerda. E, para piorar a situação, às vezes eles diferenciam o resultado final, enquanto a tabela esquerda pode não precisar ser exclusiva (ou não exclusiva nas colunas que você selecionar). Além disso, oferece a chance de realmente selecionar uma coluna que não existe na tabela esquerda.

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

Erros mais comuns:

  1. Juntando-se diretamente no T2, sem uma subconsulta segura. Resultando em risco de duplicação)
  2. SELECT * (garantido para obter colunas de T2)
  3. SELECT c (não garante que sua coluna venha e sempre venha de T1)
  4. Não DISTINCT ou DISTINCT no lugar errado

CONCATENAÇÃO DE COLUNAS COM SEPARADOR (Desempenho não muito seguro, horrível)

O problema funcional é que, se você usar um separador que pode ocorrer em uma coluna, fica complicado garantir que o resultado seja 100% exato. O problema técnico é que esse método geralmente gera conversões de tipo e ignora completamente os índices, resultando em um desempenho possivelmente horrível. Apesar desses problemas, tenho que admitir que às vezes ainda o uso para consultas ad-hoc em pequenos conjuntos de dados.

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

Observe que, se suas colunas forem numéricas, alguns dialetos SQL exigirão que você as converta em cadeias primeiro. Acredito que o SQL Server fará isso automaticamente.


Para finalizar: Como de costume, existem muitas maneiras de fazer isso no SQL, usar opções seguras evitará surpresas e economizará tempo e dinheiro a longo prazo.

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

Nota:
Oracle ignora as linhas em que uma ou mais das colunas selecionadas são NULL. Nesses casos, você provavelmente deseja usar o NVL -Funktion para mapear NULL para um valor especial (que não deve estar nos valores);

select * from tab1
where (col1, NVL(col2, '---') in (select col1, NVL(col2, '---') from tab2)
tommes-pommes
fonte
2
O postgres suporta, where (colA,colB) in (... some list of tuples...)mas não tenho certeza do que outros bancos de dados fazem o mesmo. Eu estaria interessado em saber.
Max Murphy
2
Essa sintaxe também é suportada no Oracle e no DB2 / 400 (provavelmente também no DB2). Gostaria que o SQL Server o suportasse.
CrazyIvan1974
O DB2 suporta isso.
Telmo Marques
Até o SQLite suporta isso.
precisa saber é o seguinte
13

Uma simples cláusula EXISTS é a mais limpa

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 você tiver várias linhas na correlação, um JOIN fornecerá várias linhas na saída, portanto, você precisará de distintas. O que geralmente torna os EXISTS mais eficientes.

Nota SELECT *com um JOIN também incluiria colunas das tabelas de limitação de linhas

gbn
fonte
2

Por que usar WHERE EXISTS ou DERIVED TABLES quando você pode apenas fazer uma junção interna normal:

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 o par de (CM_PLAN_ID, Individual_ID) não for exclusivo na tabela de status, você poderá precisar de um SELECT DISTINCT t. *.

BradC
fonte
3
E o DISTINCT normalmente significa um EXISTE é mais eficiente
GBN
0
Postgres SQL  : version 9.6
Total records on tables : mjr_agent = 145, mjr_transaction_item = 91800

1.Usando com EXISTS[Tempo médio de consulta: 1,42s]

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.Usando com duas linhas INCláusula [Tempo médio de consulta: 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.Usando com INNNER JOINpadrão [Tempo Médio de Consulta: 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

Então, eu escolhi a segunda opção.

Cataclismo
fonte
Aviso para futuros leitores: De acordo com a pergunta, você provavelmente desejará usar ANDdeclarações em vez de ORdeclarações.
Dennis Jaheruddin 27/01/19
@DennisJaheruddin .. Obrigado pelo seu comentário e explicações detalhadas muito agradáveis ​​da sua resposta. Você está certo, a ORdeclaração provavelmente gera duplicações. No meu caso, não há nenhuma linha que contenha o mesmo src_ide dest_idem uma única linha. Portanto, duplicações não acontecerão no meu caso.
Cataclysm
-2

Se você deseja uma tabela, use a seguinte consulta

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 tabela de dados como 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

Em seguida, faça a saída da seguinte forma

id|name|adde|city
1   a   ad  ca
2   b   bd  bd
Somnath Kadam
fonte
id in (1,2) and studentName in ('a','b')totalmente não é o mesmo que (id, studentName) in ((1,'a'),(2,'b')). Pense em um registro com id = 2 e nome = 'a'. Obviamente, se o ID for único, o efeito será diminuído, mas, se o ID for único, não precisaremos filtrar os nomes.
quetzalcoatl
-2

Nós podemos simplesmente fazer isso.

   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
fonte
-2

Concatenar as colunas juntas de alguma forma é um "hack", mas quando o produto não suporta semi-junções para mais de uma coluna, às vezes você não tem escolha.

Exemplo de onde a solução de junção interna / externa não funcionaria:

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

Quando as consultas não são triviais por natureza, às vezes você não tem acesso à tabela base definida para realizar junções internas / externas regulares.

Se você usar esse "hack", quando combinar os campos, adicione um delimitador suficiente entre eles para evitar interpretações erradas, por exemplo, ColA + ":-:" + ColB

John K
fonte
Esta resposta parece ser inconsistente (menciona concatenação e, em seguida, fornece um exemplo diferente). Além disso, em uma nota mais clara: sempre temos uma opção ;-) Adicionei o exemplo de concatenação à minha visão geral aqui, com as notas de rodapé relevantes: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin
-3

Eu fundei mais fácil assim

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 
) 

Espero que esta ajuda :)

Lisandro Acosta
fonte
9
Ai, nenhum índice usado aqui faz para a string concat.
mrdenny
9
Eu votei para baixo, pois é claramente perigoso! Se CM_PLAN_ID = 45e Individual_ID = 3, em seguida, concatenação resulta em 453- que se confunde com o caso em que CM_PLAN_ID = 4e Individual_ID = 53... pedindo problema Eu teria pensado
El Ronnoco
5
... é claro que você pode concatenar com um caractere especial arbitrário, por exemplo, 45_3ou 45:3ainda não é uma solução agradável e, é claro, como @mrdenny diz que os índices não serão utilizados agora que uma transformação ocorreu nas colunas.
El Ronnoco 25/02
1
Também votei contra, pois essa solução é realmente apenas um "hack" rápido. É lento e, como El Ronnoco disse, pode levar a erros.
-4

A maneira simples e errada seria combinar duas colunas usando + ou concatenar e criar uma coluna.

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

Isso seria muito lento. Não pode ser usado na programação, mas se você estiver apenas consultando para verificar se algo pode ser usado.

Vijay
fonte
10
De fato, e isso pode levar a erros, uma vez que, por exemplo, 'ab' + 'c' = 'a' + 'bc'