使用错误的列名执行查询时没有错误
No error while executing a query with wrong column name
我遇到了奇怪的行为,至少在 SQL 服务器上(我仍然需要检查其他 SQL 引擎),同时尝试删除一些记录。
我在 SQL 服务器实例上测试了以下内容:
- Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) 2019 年 9 月 24 日 13:48:23 版权所有 (C) 2019 Microsoft Corporation Developer Edition(64 位)在 Windows 10 企业版 2016 LTSB 10.0(内部版本 14393:)
- Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) 2016 年 10 月 28 日 18:17:30 版权所有 (c) Microsoft Corporation Standard Edition(64 位)在 Windows 服务器 2012 R2 标准 6.3(内部版本 9600:)(管理程序)
这是一个 SQL 代码片段。这是我尝试做的简化版本,肯定可以讨论为什么要这样查询,但我的观点不同——为什么会这样。
drop table if exists #A
drop table if exists #B
create table #B (id char, foo char) -- I use different types for id columns just for the sake of readability
create table #A (id int, b_id char) -- b_id is a link to #A, _not_ specified as FK
insert into #B values('x', 'l')
insert into #B values('y', 'm')
insert into #A values(0, 'x')
insert into #A values(1, 'z')
insert into #A values(2, 'z')
insert into #A values(3, 'y')
insert into #A values(4, 'y')
insert into #A values(5, 'y')
-- there are 2 parent records in #B and 3 child records for each of them in #A
select * from #A -- just to check, all good the data is there, all as expected, no problem
-- now the fun part
--(X) the following query, rightfully gives an error, as column b_id does not exist in #B
-- select b_id from #B where foo='l'
--(Y) the following query gives empty result, whereas I would expect an error:
select * from #A where b_id in (select b_id from #B where foo='l')
-- I can imagine that this has something to do with the fact that b_id exists in table #A in outer query
--(Z) the following query deletes(!) all data in table #A:
delete from #A where b_id in (select b_id from #B where foo='l')
select * from #A
-- once again, I can imagine that there is no error message "Invalid column name 'b_id'." because b_id exists in table #A in outer query
所以这是我的问题:
- 为什么在查询(Y)和(Z)中没有关于无效列的错误消息?我会对细节感兴趣
- 根据答案 (1),了解为什么查询 (Y) 提供空结果会很有趣。很明显,如果inner select 为空,那么outer 也应该为空,但是魔鬼藏在细节中
- 为什么查询 (Z) 会删除 table #A 中的所有记录?我希望受影响的记录(在 (Y) 的情况下返回,在 (Z) 的情况下删除)应该是相同的
查询
select * from #A where b_id in (select b_id from #B where foo='l')
在子查询中使用来自 #A 的 b_id。
如果您想产生错误,您需要添加 table 名称:
select * from #A where b_id in (select #B.b_id from #B where foo='l')
这会给你:"Invalid column name 'b_id'."
我无法使用给定的脚本重现您的案例 'Y'。两个查询 return 相同的结果:
select * from #A
select * from #A where b_id in (select b_id from #B where foo='l')
问题 1 (Y):
(实时和实际的)执行计划表明您的想象是正确的:显示了每个节点的输出,包括 table 前缀 - 不会混淆:
节点3returns下面两列:[#A].[id], [#A].[b_id]
,而节点4没有什么return而是NULL
/*
id b_id
----------- ----
0 x
1 z
2 z
3 y
4 y
5 y
(6 rows affected)
Table '#B__________________________________________________________________________________________________________________000000000335'. Scan count 1, logical reads 6, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
Table '#A__________________________________________________________________________________________________________________000000000336'. Scan count 1, logical reads 1, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
Rows Executes StmtText NodeId Parent PhysicalOp LogicalOp Argument DefinedValues EstimateRows EstimateIO EstimateCPU AvgRowSize TotalSubtreeCost OutputList Warnings Type EstimateExecutions
---- -------- ------------------------------------------------------------------------------------ ------ ------ ------------ -------------- -------------------------------------------------------------- ----------------------- ------------- ------------- ------------- ----------- ---------------- ----------------------- -------- --------- ------------------
6 1 select * from #A where b_id in (select b_id from #B where foo='l') 1 0 NULL NULL NULL NULL 6 NULL NULL NULL 0,00701002 NULL NULL SELECT NULL
6 1 |--Nested Loops(Left Semi Join) 2 1 Nested Loops Left Semi Join NULL NULL 6 0 2,508E-05 12 0,00701002 [#A].[id], [#A].[b_id] NULL PLAN_ROW 1
6 1 |--Table Scan(OBJECT:([tempdb].[dbo].[#A]), WHERE:([#A].[b_id]=[#A].[b_id])) 3 2 Table Scan Table Scan OBJECT:([tempdb].[dbo].[#A]), WHERE:([#A].[b_id]=[#A].[b_id]) [#A].[id], [#A].[b_id] 6 0,003125 0,0001636 12 0,0032886 [#A].[id], [#A].[b_id] NULL PLAN_ROW 1
6 6 |--Table Scan(OBJECT:([tempdb].[dbo].[#B]), WHERE:([#B].[foo]='l')) 4 2 Table Scan Table Scan OBJECT:([tempdb].[dbo].[#B]), WHERE:([#B].[foo]='l') NULL 1 0,0032035 8,07E-05 9 0,0036877 NULL NULL PLAN_ROW 6
(4 rows affected)
*/
问题 2:查询得到 return 个结果。
问题 3:
SELECT * FROM #A WHERE b_id IN (SELECT b_id FROM #B WHERE foo = 'l');
SELECT * FROM #A WHERE EXISTS (SELECT 'YAAAY' FROM #B WHERE foo = 'l');
SELECT * FROM #A WHERE EXISTS (SELECT 'YAAAY' FROM #B WHERE foo = 'asdafadsf');
我认为 OP 的查询可以重写为等效的 EXISTS 查询。
b_id
将始终等于 b_id
,除非 IN ()
部分没有结果 return。
你说得对#A.b_id 是罪魁祸首。查询
select * from #A where b_id in (select b_id from #B where foo='l')
翻译成
select * from #A where b_id in (select #A.b_id from #B where #B.foo='l')
假设#B 没有包含 foo = 'l' 的行。然后子查询 return 没有行,整个查询没有 return 任何行,因为条件变为 where b_id in (<nothing>)
并且不满足。
另一方面,如果#B 有 foo = 'l' 的行,子查询 returns #A.b_id 用于每个这样的行。在这种情况下,查询 return 的所有行,因为条件变为 where b_id in (b_id, b_id, b_id, ...)
并得到满足。
所以,似乎在尝试 (Y) 时没有带有 foo = 'l' 的 #B 行,但在尝试 (Z) 时却有。
我遇到了奇怪的行为,至少在 SQL 服务器上(我仍然需要检查其他 SQL 引擎),同时尝试删除一些记录。
我在 SQL 服务器实例上测试了以下内容:
- Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) 2019 年 9 月 24 日 13:48:23 版权所有 (C) 2019 Microsoft Corporation Developer Edition(64 位)在 Windows 10 企业版 2016 LTSB 10.0(内部版本 14393:)
- Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) 2016 年 10 月 28 日 18:17:30 版权所有 (c) Microsoft Corporation Standard Edition(64 位)在 Windows 服务器 2012 R2 标准 6.3(内部版本 9600:)(管理程序)
这是一个 SQL 代码片段。这是我尝试做的简化版本,肯定可以讨论为什么要这样查询,但我的观点不同——为什么会这样。
drop table if exists #A
drop table if exists #B
create table #B (id char, foo char) -- I use different types for id columns just for the sake of readability
create table #A (id int, b_id char) -- b_id is a link to #A, _not_ specified as FK
insert into #B values('x', 'l')
insert into #B values('y', 'm')
insert into #A values(0, 'x')
insert into #A values(1, 'z')
insert into #A values(2, 'z')
insert into #A values(3, 'y')
insert into #A values(4, 'y')
insert into #A values(5, 'y')
-- there are 2 parent records in #B and 3 child records for each of them in #A
select * from #A -- just to check, all good the data is there, all as expected, no problem
-- now the fun part
--(X) the following query, rightfully gives an error, as column b_id does not exist in #B
-- select b_id from #B where foo='l'
--(Y) the following query gives empty result, whereas I would expect an error:
select * from #A where b_id in (select b_id from #B where foo='l')
-- I can imagine that this has something to do with the fact that b_id exists in table #A in outer query
--(Z) the following query deletes(!) all data in table #A:
delete from #A where b_id in (select b_id from #B where foo='l')
select * from #A
-- once again, I can imagine that there is no error message "Invalid column name 'b_id'." because b_id exists in table #A in outer query
所以这是我的问题:
- 为什么在查询(Y)和(Z)中没有关于无效列的错误消息?我会对细节感兴趣
- 根据答案 (1),了解为什么查询 (Y) 提供空结果会很有趣。很明显,如果inner select 为空,那么outer 也应该为空,但是魔鬼藏在细节中
- 为什么查询 (Z) 会删除 table #A 中的所有记录?我希望受影响的记录(在 (Y) 的情况下返回,在 (Z) 的情况下删除)应该是相同的
查询
select * from #A where b_id in (select b_id from #B where foo='l')
在子查询中使用来自 #A 的 b_id。
如果您想产生错误,您需要添加 table 名称:
select * from #A where b_id in (select #B.b_id from #B where foo='l')
这会给你:"Invalid column name 'b_id'."
我无法使用给定的脚本重现您的案例 'Y'。两个查询 return 相同的结果:
select * from #A
select * from #A where b_id in (select b_id from #B where foo='l')
问题 1 (Y):
(实时和实际的)执行计划表明您的想象是正确的:显示了每个节点的输出,包括 table 前缀 - 不会混淆:
节点3returns下面两列:[#A].[id], [#A].[b_id]
,而节点4没有什么return而是NULL
/*
id b_id
----------- ----
0 x
1 z
2 z
3 y
4 y
5 y
(6 rows affected)
Table '#B__________________________________________________________________________________________________________________000000000335'. Scan count 1, logical reads 6, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
Table '#A__________________________________________________________________________________________________________________000000000336'. Scan count 1, logical reads 1, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
Rows Executes StmtText NodeId Parent PhysicalOp LogicalOp Argument DefinedValues EstimateRows EstimateIO EstimateCPU AvgRowSize TotalSubtreeCost OutputList Warnings Type EstimateExecutions
---- -------- ------------------------------------------------------------------------------------ ------ ------ ------------ -------------- -------------------------------------------------------------- ----------------------- ------------- ------------- ------------- ----------- ---------------- ----------------------- -------- --------- ------------------
6 1 select * from #A where b_id in (select b_id from #B where foo='l') 1 0 NULL NULL NULL NULL 6 NULL NULL NULL 0,00701002 NULL NULL SELECT NULL
6 1 |--Nested Loops(Left Semi Join) 2 1 Nested Loops Left Semi Join NULL NULL 6 0 2,508E-05 12 0,00701002 [#A].[id], [#A].[b_id] NULL PLAN_ROW 1
6 1 |--Table Scan(OBJECT:([tempdb].[dbo].[#A]), WHERE:([#A].[b_id]=[#A].[b_id])) 3 2 Table Scan Table Scan OBJECT:([tempdb].[dbo].[#A]), WHERE:([#A].[b_id]=[#A].[b_id]) [#A].[id], [#A].[b_id] 6 0,003125 0,0001636 12 0,0032886 [#A].[id], [#A].[b_id] NULL PLAN_ROW 1
6 6 |--Table Scan(OBJECT:([tempdb].[dbo].[#B]), WHERE:([#B].[foo]='l')) 4 2 Table Scan Table Scan OBJECT:([tempdb].[dbo].[#B]), WHERE:([#B].[foo]='l') NULL 1 0,0032035 8,07E-05 9 0,0036877 NULL NULL PLAN_ROW 6
(4 rows affected)
*/
问题 2:查询得到 return 个结果。
问题 3:
SELECT * FROM #A WHERE b_id IN (SELECT b_id FROM #B WHERE foo = 'l');
SELECT * FROM #A WHERE EXISTS (SELECT 'YAAAY' FROM #B WHERE foo = 'l');
SELECT * FROM #A WHERE EXISTS (SELECT 'YAAAY' FROM #B WHERE foo = 'asdafadsf');
我认为 OP 的查询可以重写为等效的 EXISTS 查询。
b_id
将始终等于 b_id
,除非 IN ()
部分没有结果 return。
你说得对#A.b_id 是罪魁祸首。查询
select * from #A where b_id in (select b_id from #B where foo='l')
翻译成
select * from #A where b_id in (select #A.b_id from #B where #B.foo='l')
假设#B 没有包含 foo = 'l' 的行。然后子查询 return 没有行,整个查询没有 return 任何行,因为条件变为 where b_id in (<nothing>)
并且不满足。
另一方面,如果#B 有 foo = 'l' 的行,子查询 returns #A.b_id 用于每个这样的行。在这种情况下,查询 return 的所有行,因为条件变为 where b_id in (b_id, b_id, b_id, ...)
并得到满足。
所以,似乎在尝试 (Y) 时没有带有 foo = 'l' 的 #B 行,但在尝试 (Z) 时却有。