SQL WHERE .. IN предложение несколько столбцов


173

Мне нужно реализовать следующий запрос в 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
)

Но предложение WHERE..IN допускает только 1 столбец. Как я могу сравнить 2 или более столбцов с другим внутренним SELECT?


Я попытался дать обзор соответствующих решений, с необходимыми предостережениями здесь: stackoverflow.com/a/54389589/983722
Деннис Джарудудин

Ответы:


110

Вы можете создать производную таблицу из подзапроса и присоединить table1 к этой производной таблице:

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
или, в более общем случае, SELECT * FROM таблицы INNER JOIN otherTable ON (table.x = otherTable.a AND table.y = otherTable.b)
аля

4
Как насчет нескольких строк, которые существуют, если таблица 2 является дочерней по отношению к таблице 1? И почему осталось присоединиться?
ГБН

1
Да, INNER JOIN будет более производительным здесь. Выполнение LEFT JOIN и фильтрация пустых значений из таблицы 2 - это просто подробный способ использования
INNER

Неправильно, это доставляет строку несколько раз, предполагая, что объединенная таблица может быть объединена несколько раз ... в противном случае, выполните внутреннее объединение, и вы можете сэкономить себе, где.
Стефан Штайгер

123

Вместо этого вы захотите использовать синтаксис 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
Хотя это сработает, он преобразует некоррелированный запрос в вопросе в коррелированный запрос. Если оптимизатор запросов не является умным, это может дать вам производительность O (n ^ 2) :-(. Но, возможно, я недооцениваю оптимизатор ...
sleske

1
Я использую подобные синтаксисы все время без проблем. Если вы не используете более старый оптимизатор (6.5, 7, 8 и т. Д.), У него не должно быть проблем с этим синтаксисом.
Мрденный

1
@sleske: EXISTS намного лучше: см. мои комментарии в моем ответе. И проверить это первым. @mrdenny: сначала я неправильно понял ваш ответ, я бы тоже использовал EXISTS
gbn

6
Это наиболее эффективно, +1. См. Эту статью в моем блоге для сравнения производительности: объяснительный
xtended.com/2009/06/17/efficient-exists

1
Даже SQL 2000 мог обрабатывать большинство коррелированных подзапросов, не превращая запрос в O (n ^ 2). Возможно, проблема была на 6.5.
GilaMonster

14

ВНИМАНИЕ О РЕШЕНИЯХ:

МНОГИЕ СУЩЕСТВУЮЩИЕ РЕШЕНИЯ ДАЮТ НЕПРАВИЛЬНЫЙ ВЫХОД, ЕСЛИ СТРОКИ НЕ УНИКАЛЬНЫ

Если вы являетесь единственным человеком, создающим таблицы, это может быть неактуально, но несколько решений будут давать различное количество выходных строк из рассматриваемого кода, когда одна из таблиц может не содержать уникальных строк.

ПРЕДУПРЕЖДЕНИЕ О ЗАЯВЛЕНИИ ЗАДАЧИ:

В НЕСКОЛЬКИХ КОЛОННАХ НЕ СУЩЕСТВУЕТ, ВНИМАТЕЛЬНО ПУНИТЕ, ЧТО ВЫ ХОТИТЕ

Когда я вижу вход с двумя столбцами, я могу представить, что это означает две вещи:

  1. Значения столбца a и столбца b появляются в другой таблице независимо
  2. Значения столбца a и столбца b появляются в другой таблице вместе в одной строке

Сценарий 1 довольно тривиален, просто используйте два оператора IN.

В соответствии с большинством существующих ответов, я настоящим предоставляю обзор упомянутых и дополнительных подходов для Сценария 2 (и краткое суждение):

EXISTS (Безопасный, рекомендуется для SQL Server)

Согласно @mrdenny, EXISTS звучит именно так, как вы ищете, вот его пример:

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

LEFT SEMI JOIN (Безопасно, рекомендуется для диалектов, которые его поддерживают)

Это очень краткий способ присоединения, но, к сожалению, большинство диалектов SQL, включая SQL-сервер, в настоящее время не поддерживают его.

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

Несколько операторов IN (безопасно, но остерегайтесь дублирования кода)

Как уже упоминалось @cataclysm, использование двух операторов IN также может помочь, возможно, даже превзойдет другие решения. Тем не менее, вы должны быть очень осторожны с дублированием кода. Если вы когда-нибудь захотите выбрать из другой таблицы или изменить инструкцию where, это увеличит риск того, что вы создадите несоответствия в своей логике.

Основное решение

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

Решение без дублирования кода (я считаю, что это не работает в обычных запросах 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)

ВНУТРЕННЕЕ СОЕДИНЕНИЕ (технически это можно сделать безопасным, но часто это не делается)

Причина, по которой я не рекомендую использовать внутреннее объединение в качестве фильтра, заключается в том, что на практике люди часто допускают дубликаты в правой таблице, которые вызывают дубликаты в левой таблице. И что еще хуже, они иногда делают конечный результат отличным, в то время как левая таблица может фактически не быть уникальной (или не уникальной в выбранных вами столбцах). Кроме того, это дает вам возможность фактически выбрать столбец, который не существует в левой таблице.

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

Самые распространенные ошибки:

  1. Присоединение непосредственно к T2, без безопасного подзапроса. В результате чего возникает риск дублирования)
  2. SELECT * (гарантированно получить столбцы из T2)
  3. SELECT c (не гарантирует, что ваш столбец будет приходить и всегда будет поступать из T1)
  4. Нет DISTINCT или DISTINCT в неправильном месте

КОНКАТЕНЦИЯ КОЛОНН С СЕПАРАТОРОМ (не очень безопасно, ужасная производительность)

Функциональная проблема заключается в том, что если вы используете разделитель, который может находиться в столбце, будет сложно убедиться, что результат будет на 100% точным. Техническая проблема заключается в том, что этот метод часто выполняет преобразования типов и полностью игнорирует индексы, что может привести к ужасной производительности. Несмотря на эти проблемы, я должен признать, что иногда я все еще использую его для специальных запросов в небольших наборах данных.

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

Обратите внимание, что если ваши столбцы являются числовыми, некоторые диалекты SQL потребуют от вас сначала привести их к строкам. Я верю, что SQL-сервер сделает это автоматически.


Подводя итог: как обычно, в SQL есть много способов сделать это, использование безопасных вариантов позволит избежать неожиданностей и сэкономит ваше время и трудности в долгосрочной перспективе.


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

Примечание.
Oracle игнорирует строки, в которых один или несколько выбранных столбцов имеют значение NULL. В этих случаях вы , вероятно , хотите, чтобы использовать NVL -Funktion для отображения NULL в специальное значение (которое не должно быть в значениях);

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

2
Postgres поддерживает, where (colA,colB) in (... some list of tuples...)но я не уверен, что другие базы данных делают то же самое. Мне было бы интересно узнать.
Макс Мерфи

2
Этот синтаксис также поддерживается в Oracle и DB2 / 400 (возможно, в DB2). Жаль, что SQL Server не поддерживает это.
CrazyIvan1974

DB2 поддерживает это.
Тельмо Маркиз

Даже SQLite поддерживает это.
Хольгер

13

Простое предложение EXISTS является самым чистым

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
)

Если у вас есть несколько строк в корреляции, то JOIN дает несколько строк в выводе, так что вам нужно отличаться. Что обычно делает EXISTS более эффективным.

Примечание SELECT *с JOIN также будет включать столбцы из таблиц ограничения строк


2

Зачем использовать ГДЕ СУЩЕСТВУЮЩИЕ или ПРОИЗВОДНЫЕ ТАБЛИЦЫ, когда вы можете просто сделать обычное внутреннее соединение:

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

Если пара (CM_PLAN_ID, Individual_ID) не уникальна в таблице состояний, вам может потребоваться вместо этого SELECT DISTINCT t. *.


3
И DISTINCT обычно означает, что EXISTS более эффективен
gbn

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

1. Использование с EXISTS[Среднее время запроса: 1,42 с]

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. Использование с двумя строками INСтатья [Среднее время запроса: 0,37 с]

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. Использование с INNNER JOINшаблоном [Среднее время запроса: 2,9 с]

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

Итак, я выбрал второй вариант.


Предупреждение для будущих читателей: в соответствии с вопросом вы, вероятно, захотите использовать ANDутверждения, а не ORутверждения.
Деннис Джаэруддин

@DennisJaheruddin .. Спасибо за ваш комментарий и очень хорошие подробные объяснения вашего ответа. Вы правы, ORзаявление, вероятно, вызовет дублирование. В моем случае нет ни одной строки, которая бы содержала то же самое src_idи dest_idв одной строке. Таким образом, дублирования не произойдет в моем случае.
Катаклизм


-2

Если вы хотите для одной таблицы, то используйте следующий запрос

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

и данные таблицы, как следует

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

Затем выведите как следует

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

id in (1,2) and studentName in ('a','b')совершенно не то же самое, что (id, studentName) in ((1,'a'),(2,'b')). Просто подумайте о записи с id = 2 и name = 'a'. Конечно, если ID уникален, то эффект уменьшается, но тогда, если ID уникален, нам вообще не нужно фильтровать имена.
Кецалькоатль

-2

Мы можем просто сделать это.

   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

Объединение столбцов вместе в некоторой форме является «хаком», но когда продукт не поддерживает полусоединения для более чем одного столбца, иногда у вас нет выбора.

Пример, где внутреннее / внешнее соединение не будет работать:

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

Когда запросы не являются тривиальными по своей природе, иногда у вас нет доступа к базовой таблице, установленной для выполнения регулярных внутренних / внешних объединений.

Если вы используете этот «хак», то при объединении полей обязательно добавьте достаточное количество разделителя между ними, чтобы избежать неправильной интерпретации, например ColA + ":-:" + ColB


Этот ответ кажется противоречивым (упоминает конкатенацию, а затем приводит другой пример). Кроме того, на более легком примечании: у нас всегда есть выбор ;-) Я добавил пример конкатенации к своему обзору здесь с соответствующими сносками: stackoverflow.com/a/54389589/983722
Деннис Джаруддин

-3

Я основал этот путь проще

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 
) 

Надеюсь это поможет :)


9
Ой, никакой индекс здесь не используется для строки concat.
Мрденни

9
Я проголосовал за это, так как это опасно! Если CM_PLAN_ID = 45и Individual_ID = 3тогда конкатенация приводит к 453- что неотличимо от случая, когда CM_PLAN_ID = 4и Individual_ID = 53...
напрашиваясь

5
... конечно, вы можете объединить с произвольным специальным символом, например, 45_3или, 45:3но это все еще не очень хорошее решение, и, конечно, как @mrdenny говорит, что индексы не будут использоваться сейчас, когда преобразование произошло в столбцах.
El Ronnoco

1
Я также проголосовал за это, так как это решение действительно только для быстрого взлома. Это медленно и, как сказал El Ronnoco, это может привести к ошибкам.

-4

Простым и неправильным способом было бы объединить два столбца, используя + или объединить и сделать один столбец.

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

Это было бы довольно медленно. Не может быть использовано в программировании, но в случае, если вы просто запрашиваете что-то для проверки, может быть использовано.


10
Действительно, и это может привести к ошибкам, так как, например, 'ab' + 'c' = 'a' + 'bc'
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.