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比较?


我尝试提供有关解决方案的概述,并在此处提供
必要的

Answers:


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的子级,将存在多行呢?为什么要左加入?
gbn

1
是的,INNER JOIN在这里会表现更好。做了LEFT JOIN和从表2滤波空值仅仅是一个冗长的方式来使用一个INNER JOIN
PSTR

错了,假设联接的表可以被联接多次,这将多次传递该行...否则,请进行内部联接,您就可以节省位置。
Stefan Steiger,

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:EXISTS到目前为止更好:请参阅我的评论。并先进行测试。@mrdenny:我一开始误读了您的答案,我也会使用EXISTS
gbn

6
这是最有效的+1。有关性能比较,请参见我的博客中的这篇文章:explainextended.com/2009/06/17/ficient-exists
Quassnoi,2009年

1
甚至SQL 2000也可以处理大多数相关的子查询,而无需将查询转换为O(n ^ 2)。回到6.5可能是个问题。
GilaMonster,2009年

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)

左半连接(安全,建议用于支持它的方言)

这是一种非常简洁的连接方式,但是不幸的是,大多数SQL方言,包括SQL Server,目前都不支持它。

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)

INNER JOIN(从技术上讲可以使其安全,但通常不会这样做)

我不建议使用内部联接作为过滤器的原因是,在实践中,人们经常让右表中的重复项导致左表中的重复项。然后使情况更糟的是,它们有时使最终结果与众不同,而实际上左表可能不需要唯一(或在您选择的列中不是唯一的)。此外,它还使您有机会实际选择左表中不存在的列。

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 Server会自动执行此操作。


总结:像往常一样,在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...)但我不确定其他数据库也可以这样做。我很想知道。
Max Murphy

2
Oracle和DB2 / 400(也可能是DB2)也支持此语法。希望SQL Server支持它。
CrazyIvan1974年

DB2支持这一点。
Telmo Marques

甚至SQLite也支持它。
Holger Jakobs

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

当您可以执行普通的内部联接时,为什么要使用WHERE EXISTS或DERIVED TABLE:

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通常意味着存在效率更高
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.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

因此,我选择了第二个选项。


对未来读者的警告:符合这个问题,您可能希望使用AND语句而不是OR语句。
丹尼斯·贾赫鲁丁

@DennisJaheruddin ..谢谢您的评论和非常详细的答案解释。您是对的,OR声明可能会引起重复。在我的情况下,还没有包含任何相同的行src_iddest_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是唯一的,我们根本不需要过滤名称。
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>
   ...

当查询本质上并不简单时,有时您无权访问基本表集以执行常规的内部/外部联接。

如果您确实使用此“ hack”,则在合并字段时,请务必在它们之间添加足够的定界符,以避免产生误解,例如 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
哎呀,这里没有索引用于字符串concat。
mrdenny

9
我将其否决,因为它很危险!如果CM_PLAN_ID = 45Individual_ID = 3再拼接的结果453-这是从那里的情况下难以区分CM_PLAN_ID = 4Individual_ID = 53...找麻烦我还以为
萨尔瓦多Ronnoco

5
..当然,您可以使用任意特殊的char进行连接,例如45_3或,45:3但是它仍然不是一个很好的解决方案,因为@mrdenny说,由于对列进行了转换,因此索引将不再被利用。
El Ronnoco

1
我也对此表示反对,因为该解决方案实际上只是一个快速的“ hack”。速度很慢,而且正如El Ronnoco所说,它可能会导致错误。

-4

简单和错误的方法是使用+组合两列或连接并形成一列。

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

这将是相当慢的路线。不能在编程中使用,但如果您只是在查询以验证某些内容,则可以使用。


10
事实上,它可能会导致错误,因为如'AB' + 'C'= 'A' + '公元前'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.