我在 MySQL 数据库中有两个表 -t1一个列c1,t2一个列c2。
我运行这个查询:
select * from t1 where c1 in (select c1 from t2);
Run Code Online (Sandbox Code Playgroud)
上面的查询应该给出一个错误,因为c1t2 中不存在。相反,它返回t1. 上述查询的另一个版本delete可能会造成更大的灾难:
delete from t1 where c1 in (select c1 from t2);
Run Code Online (Sandbox Code Playgroud)
当上面的查询只是应该给出错误时,它会删除 t1 中的所有行。
我注意到只有当子查询中的列与外部列具有相同的名称时才会发生这种行为。意义,
select * from t1 where c1 in (select c3 from t2);
Run Code Online (Sandbox Code Playgroud)
将按预期抛出错误:
ERROR 1054 (42S22): Unknown column 'c3' in 'field list'
Run Code Online (Sandbox Code Playgroud)
顺便说一句,我已经在 PostgreSQL 9.6.3 上检查过同样的问题,行为完全相同。对这种奇怪的行为有什么解释吗?
ype*_*eᵀᴹ 10
我运行这个查询:
Run Code Online (Sandbox Code Playgroud)select * from t1 where c1 in (select c1 from t2);上面的查询应该给出一个错误,因为
c1t2 中不存在。相反,它返回t1.
不,查询不应该给出错误。这是一个常见的错误(认为c1in(select c1 from t2)指的是t2。它不是由于范围解析,即列名是如何解析的(如何找到它们所指的表)。查询:
select * from t1 where c1 in (select c1 from t2);
Run Code Online (Sandbox Code Playgroud)
可以解析为三个不同的选项:
当t2有一个名为 的列时c1,它运行为:
select * from t1 where c1 in (select t2.c1 from t2);
Run Code Online (Sandbox Code Playgroud)whent2没有名为 的列c1,但t1有,它运行为:(
这是您的情况!)
select * from t1 where c1 in (select t1.c1 from t2);
Run Code Online (Sandbox Code Playgroud)当既没有t2也t1没有名为 的列时c1,它会抛出错误:
select * from t1 where c1 in (select c1 from t2);
Run Code Online (Sandbox Code Playgroud)
-- 抛出错误(“未知列 c1”或类似的东西)
上述查询的另一个版本
delete可能会造成更大的灾难:Run Code Online (Sandbox Code Playgroud)delete from t1 where c1 in (select c1 from t2);当上面的查询只是应该给出错误时,它会删除 t1 中的所有行。
出于同样的原因,没有。查询解析为并运行为:
delete from t1 where c1 in (select t1.c1 from t2);
Run Code Online (Sandbox Code Playgroud)
所以t1只要t2表不为空,它就会删除所有行。
如何避免这些问题?
始终使用表名作为列引用的前缀。通过这样做,您将始终获得所需的结果,或者如果该列未出现在您为其添加前缀的表中,则会出现错误。
您的查询应该是:
select t1.* from t1 where t1.c1 in (select t2.c1 from t2);
delete from t1 where t1.c1 in (select t2.c1 from t2);
Run Code Online (Sandbox Code Playgroud)
如果c1table 中没有列,它们都会抛出错误t2。
小智 6
这实际上不是错误。您实际上是在引用外部表中的列。
由于此“功能”,您可以编写相关子查询并在内部查询中引用外部查询中的字段。
SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 where c1=c2)
Run Code Online (Sandbox Code Playgroud)
通常这用于内部查询 WHERE 子句,但没有理由不用于 SELECT 部分。例如,您可能希望将外部查询中的字段与内部字段连接或添加以获得结果。
SELECT * FROM t1 WHERE total IN (SELECT c1+c2 FROM t2 WHERE c2>c1)
Run Code Online (Sandbox Code Playgroud)
上面的内容令人困惑,因此防止您描述的错误的最佳方法是在所有字段中添加带有表名的字段前缀。
SELECT t1.c1 FROM t1 WHERE t1.c1 IN (SELECT t2.c2 FROM t2)
Run Code Online (Sandbox Code Playgroud)
现在如果你错误地将 t2.c1 放在子查询中,你会得到一个错误。
其他查询也将更容易理解:
SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 where t1.c1=t2.c2)
SELECT * FROM t1 WHERE t1.total IN (SELECT t1.c1+t2.c2 FROM t2 WHERE t2.c2>t1.c1)
Run Code Online (Sandbox Code Playgroud)
在早期编写查询时养成这种习惯是很好的,因为在更复杂的数据库中,总会有具有相同或相似名称的字段(主键通常总是“id”等),这可能会导致严重的错误。这不仅是错误删除的问题,而且多年来我看到重要的报告给出了错误的数字。