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?


Я спробував уявити огляд відповідних рішень, з neccesary застережень тут: stackoverflow.com/a/54389589/983722
Dennis Jaheruddin

Відповіді:


110

Ви можете зробити похідну таблицю з підзапиту та приєднати таблицю1 до цієї похідної таблиці:

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
або більш загально ВИБІР * ВІД таблиці ВНУТРІШНЯ ПРИЄДНАЙТЕСЬ до іншогоTable ON (table.x = otherTable.a AND table.y = otherTable.b)
ала

4
Як щодо кількох рядків, які існували б, якщо таблиця 2 є дочіркою таблиці 1? І чому ЛІВО ПРИЄДНАЙТЕСЬ?
gbn

1
Так, INNER JOIN був би більш ефективним тут.
Перехід до лівого приєднання

Неправильно, це доставляє ряд кілька разів, припускаючи, що об'єднану таблицю можна приєднати кілька разів ... інакше зробіть внутрішнє з'єднання, і ви можете пощадити собі куди.
Стефан Штайгер

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 тощо), він не повинен мати проблем із цим синтаксисом.
mrdenny

1
@sleske: ВИСТАВКИ набагато краще: дивіться мої коментарі у моїй відповіді. І тестуйте це спочатку,. @mrdenny: Я спочатку неправильно прочитав вашу відповідь, я також використовую EXISTS
gbn

6
Це найбільш ефективно, +1. Дивіться цю статтю у своєму блозі для порівняння продуктивності: explainextended.com/2009/06/17/efficient-exists
Quassnoi

1
Навіть SQL 2000 міг обробляти більшість корельованих підзапитів, не перетворюючи запит на O (n ^ 2). Можливо, проблема була 6.5.
GilaMonster

14

ПОПЕРЕДЖЕННЯ ПРО РІШЕННЯ:

МНОГО ІСНУЮЧІ РІШЕННЯ ДАВАТИСЬ НЕВЕРСІЙНИЙ ВІДКЛЮЧЕННЯ, ЯКЩО РУДИ НЕ БУДУТЬ Унікальними

Якщо ви є єдиною особою, яка створює таблиці, це може не бути актуальним, але декілька рішень дадуть різну кількість вихідних рядків від відповідного коду, коли одна з таблиць може не містити унікальних рядків.

ПОПЕРЕДЖЕННЯ ПРО ЗАЯВКУ ПРОБЛЕМИ:

В МНОГО СТРУМКАХ НЕ ІСНУЄТЬСЯ, ДУМАЙТЕ ДЕРЖАВНО, ЧОГО Ви хочете

Коли я бачу вкладку з двома стовпцями, я можу уявити, що це означає дві речі:

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

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

Відповідно до більшості існуючих відповідей, я надаю огляд згаданих та додаткових підходів до сценарію 2 (і коротке судження):

ДОДАТИ (Безпечно, рекомендується для SQL Server)

Як надає @mrdenny, EXISTS звучить саме так, як ви шукаєте, ось його приклад:

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

ЛЕВИЙ SEMI ПРИЄДНАЙТЕСЬ (Безпечний, рекомендується для діалектів, які його підтримують)

Це дуже стислий спосіб приєднатися, але, на жаль, більшість діалектів 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 *з приєднанням також міститиме стовпці з обмежувальних рядків


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,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. Використання двох рядків INзастереження [Середній час запиту: 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.Використання з 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 унікальний, то ефект зменшується, але тоді, якщо ідентифікатор унікальний, нам взагалі не потрібно фільтрувати імена.
quetzalcoatl

-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
Dennis Jaheruddin

-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
Ой, тут не використовується індекс, що робиться до концети рядка
mrdenny

9
Я проголосував за це, як це просто небезпечно! Якщо CM_PLAN_ID = 45і Individual_ID = 3потім конкатенація призводить до того, 453що - нічим не відрізняється від випадку, коли CM_PLAN_ID = 4і Individual_ID = 53... просити проблеми, я б міг подумати
El Ronnoco

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

1
Я також проголосував за це, оскільки це рішення справді є швидким "злому". Це повільно, і, як сказав Ель Ронноко, це може призвести до помилок.

-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.