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.id
或 Person.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
在子查询中没有列ID
和ID
实际上意味着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 查询产生奇怪的结果的主要内容,如果未能解决你的问题,请参考以下文章