对于与外部查询中同名的不存在的列,子查询不会出错

Alo*_*wal 7 subquery

我在 MySQL 数据库中有两个表 -t1一个列c1t2一个列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

我运行这个查询:

select * from t1 where c1 in (select c1 from t2);
Run Code Online (Sandbox Code Playgroud)

上面的查询应该给出一个错误,因为c1t2 中不存在。相反,它返回t1.

不,查询不应该给出错误。这是一个常见的错误(认为c1in(select c1 from t2)指的是t2。它不是由于范围解析,即列名是如何解析的(如何找到它们所指的表)。查询:

select * from t1 where c1 in (select c1 from t2);
Run Code Online (Sandbox Code Playgroud)

可以解析为三个不同的选项:

上述查询的另一个版本delete可能会造成更大的灾难:

delete from t1 where c1 in (select c1 from t2);
Run Code Online (Sandbox Code Playgroud)

当上面的查询只是应该给出错误时,它会删除 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

  • 虽然我了解此功能的_如何_(由于范围解析),但我只能想出非常人为的示例,以使此行为在实际数据库中显得有用(而不是一个非常微妙但灾难性的错误机会)。我是否缺少一些可以从子查询范围之外选择子查询中的列的明显用例?[该行为存在于其他 SQL 平台中](/sf/ask/2072849551/),所以它似乎是可以接受的习俗。 (2认同)
  • 谢谢回复!我的评论更多的是猜测是否有充分的理由开始这种行为。当在过滤条件中使用外部值时,相关子查询是有意义的,尽管通过子查询从外部查询中具体_选择_值更加困难 - 如果您想要的话,您可以在外部中选择它们,或者利用存在子句代替(如果尝试执行过滤的“IN”子句)。无论如何,我确信比我聪明的人有充分的理由允许这种行为,我只是很难看到它。 (2认同)

小智 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”等),这可能会导致严重的错误。这不仅是错误删除的问题,而且多年来我看到重要的报告给出了错误的数字。