SQL IN 查询产生奇怪的结果

Posted

技术标签:

【中文标题】SQL IN 查询产生奇怪的结果【英文标题】:SQL IN query produces strange result 【发布时间】:2013-09-02 16:41:57 【问题描述】:

请看下表结构:

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。为什么上面的查询会产生结果?我希望它会出错,因为我不会按顺序存在。

【问题讨论】:

ID [order]中(你有OID & PID 它不会返回两个结果。 @AlexK,这就是重点。我预计查询会出错,但不会。 我不认为这是一个坏问题......是的,这是一个错字,但我也希望它会给出一个错误......也许问题的标题应该是改进 您没有错误,因为 ID 存在于 Person 中。您可以在子查询字段列表中使用主表的字段。 【参考方案1】:

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

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)

查询返回一个结果,其中列 ColB 被认为来自表 X1。

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

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?
);

【讨论】:

是的,这是一个非常明确的答案,有很多事实支持。感谢您的努力。【参考方案2】:

这里的问题是您没有在子查询中使用Table.Column 表示法,表Order 在子查询中没有列IDID 实际上意味着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] 表的架构何时更改,如果有人从[Order] 中删除列PID,那么您的查询将返回表Person 中的所有行。因此,使用别名:

select * from Person as P WHERE P.pid IN (SELECT O.PID FROM [Order] as O)

请注意 - 这不是 SQL Server 特定的行为,它是标准 SQL:

SQL Server demo PostgreSQL demo MySQL demo Oracle demo

【讨论】:

没有,我的回答是关于它。可能是我不清楚。我的回答是关于使用别名有助于避免这样的错误 谢谢+1。这是一个相关的子查询吗? 实际上我使用 SQL Server,但从未在工作中使用术语相关子查询 :) 但是,您可以像 select * from Person as P WHERE exits (SELECT * FROM [Order] as O where O.PID = P.pid) 那样重写您的示例 - 现在您的子查询看起来像相关 既然你坚持使用别名部分的"ALWAYS",你应该把select *改成select p.* @swasheck 提高可读性并使查询可预测:) 规则是不要在生产代码中使用*。当我明确指定列时,我在列名之前使用别名,* 只是为了使示例查询更短。【参考方案3】:

订单表没有id列

试试这些:

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

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

【讨论】:

以上是关于SQL IN 查询产生奇怪的结果的主要内容,如果未能解决你的问题,请参考以下文章

为啥减去这两次(在 1927 年)会产生奇怪的结果?

sql in怎么根据in的顺序排列查询结果

在我的查询中使用 Point() 会产生奇怪的结果,是我用错了还是意料之中?

SQL子查询返回奇怪的结果

为啥 SQL 子查询中的外部引用会产生不同的结果?

Sql中in和exists详解