SQL IN查询产生奇怪的结果

时间:2013-09-02 16:41:58

标签: sql sql-server sql-server-2008

请参阅下面的表结构:

CREATE TABLE Person (id int not null, PID INT NOT NULL, Name VARCHAR(50))
CREATE TABLE [Order] (OID INT NOT NULL, PID INT NOT NULL)

INSERT INTO Person VALUES (1,1,'Ian')
INSERT INTO Person VALUES (2,2,'Maria')
INSERT INTO [Order] values (1,1)

为什么以下查询会返回两个结果:

select * from Person WHERE id IN (SELECT ID FROM [Order])

订单中不存在ID。为什么上面的查询会产生结果?我希望它会出错,因为我不按顺序存在。

3 个答案:

答案 0 :(得分:11)

这种行为虽然不直观,但在微软的知识库中有很好的定义:

KB #298674 : PRB: Subquery Resolves Names of Column to Outer Tables

从那篇文章:

  

为了说明该行为,请使用以下两个表结构和查询:

CREATE TABLE X1 (ColA INT, ColB INT)
CREATE TABLE X2 (ColC INT, ColD INT)
SELECT ColA FROM X1 WHERE ColA IN (Select ColB FROM X2)
  

查询返回一个结果,其中从表X1中考虑列ColB。

     

通过限定列名称,将出现错误消息,如以下查询所示:

SELECT ColA FROM X1 WHERE ColA in (Select X2.ColB FROM X2)
  

服务器:消息207,级别16,状态3,行1   
无效的列名称'ColB'。

多年来,人们一直在抱怨这个问题,但微软并不打算解决这个问题。毕竟,它符合标准,基本上表明:

  

如果在当前作用域中找不到列x,则遍历到下一个外部作用域,依此类推,直到找到引用。

以下更多信息连接“错误”以及多个官方确认,这种行为是设计的,不会改变(所以你必须改变你的 - 即总是使用别名 ):

Connect #338468 : CTE Column Name resolution in Sub Query is not validated
Connect #735178 : T-SQL subquery not working in some cases when IN operator used
Connect #302281 : Non-existent column causes subquery to be ignored
Connect #772612 : Alias error not being reported when within an IN operator
Connect #265772 : Bug using sub select

在您的情况下,如果使用比ID,OID和PID更有意义的名称,则可能不太可能发生此“错误”。 Order.PID是否指向Person.idPerson.PID?设计您的表格,以便人们可以在不必询问您的情况下找出关系。 PersonID应始终为PersonID,无论模式位于何处;与OrderID相同。保存一些打字字符对于完全模糊的架构来说不是一个好的代价。

您可以改为编写EXISTS子句:

... FROM dbo.Person AS p WHERE EXISTS 
(
  SELECT 1 FROM dbo.[Order] AS o
  WHERE o.PID = p.id -- or is it PID? See why it pays to be explicit?
);

答案 1 :(得分:9)

这里的问题是您没有在子查询中使用Table.Column表示法,表Order没有列ID,而子查询中的ID实际上意味着{ {1}},而不是Person.ID。这就是为什么我总是坚持在生产代码中使用表的别名。比较这两个查询:

[Order].ID

第一个将执行但会返回不正确的结果,第二个将引发错误。这是因为外部查询的列可以在子查询中引用,因此在这种情况下,您可以在子查询中使用select * from Person WHERE id IN (SELECT ID FROM [Order]); select * from Person as p WHERE p.id IN (SELECT o.ID FROM [Order] as o) 列。 也许你想使用这样的查询:

Person

但是你永远不知道select * from Person WHERE pid IN (SELECT PID FROM [Order]) 表的模式何时发生变化,如果有人从[Order]中删除了列PID,那么你的查询将返回表{{1}中的所有行}}。因此,请使用别名:

[Order]

快速记下 - 这不是SQL Server特有的行为,它是标准的SQL:

答案 2 :(得分:0)

订单表没有id列请改为尝试这些:

select * from Person WHERE id IN (SELECT OID FROM [Order])

OR

select * from Person WHERE pid IN (SELECT PID FROM [Order])