每当我需要检查表中是否存在某行时,我倾向于总是写一个条件,例如:
SELECT a, b, c
FROM a_table
WHERE EXISTS
(SELECT * -- This is what I normally write
FROM another_table
WHERE another_table.b = a_table.b
)
其他一些人这样写:
SELECT a, b, c
FROM a_table
WHERE EXISTS
(SELECT 1 --- This nice '1' is what I have seen other people use
FROM another_table
WHERE another_table.b = a_table.b
)
当条件NOT EXISTS
不是EXISTS
: 在某些情况下,我可能会用 aLEFT JOIN
和一个额外的条件(有时称为antijoin)来编写它:
SELECT a, b, c
FROM a_table
LEFT JOIN another_table ON another_table.b = a_table.b
WHERE another_table.primary_key IS NULL
我尽量避免它,因为我认为含义不太清楚,特别是当您primary_key
的内容不是那么明显时,或者当您的主键或连接条件是多列时(并且您很容易忘记其中一列)。但是,有时您维护由其他人编写的代码……它就在那里。
有什么区别(除了风格)来
SELECT 1
代替SELECT *
吗?
有没有表现不同的极端情况?虽然我写的是(AFAIK)标准SQL:不同的数据库/旧版本有这样的区别吗?
明确写反加入有什么好处吗?
当代计划者/优化者是否将其与NOT EXISTS
条款区别对待?
(NOT) EXISTS (SELECT 1 ...)
不,(NOT) EXISTS (SELECT * ...)
所有主要 DBMS之间的效率没有差异。我也经常看到(NOT) EXISTS (SELECT NULL ...)
被使用。在某些情况下,您甚至可以编写
(NOT) EXISTS (SELECT 1/0 ...)
并且结果是相同的 - 没有任何(除以零)错误,这证明那里的表达式甚至没有被评估。关于
LEFT JOIN / IS NULL
antijoin 方法,更正一下:这相当于NOT EXISTS (SELECT ...)
.在这种情况下,
NOT EXISTS
vsLEFT JOIN / IS NULL
,你可能会得到不同的执行计划。例如,在 MySQL 中,主要是在旧版本(5.7 之前)中,计划非常相似,但并不完全相同。据我所知,其他 DBMS(SQL Server、Oracle、Postgres、DB2)的优化器或多或少能够重写这两种方法并为两者考虑相同的计划。尽管如此,仍然没有这样的保证,并且在进行优化时,最好检查来自不同等效重写的计划,因为可能存在每个优化器不重写的情况(例如,复杂查询,具有许多连接和/或派生表/子查询中的子查询,其中来自多个表的条件、连接条件中使用的复合列)或优化器选择和计划受可用索引、设置等的不同影响。另请注意,
USING
不能在所有 DBMS 中使用(例如 SQL Server)。比较常见的JOIN ... ON
作品无处不在。并且列需要以表名/别名为前缀,
SELECT
以避免在我们进行连接时出现错误/歧义。我通常也更喜欢将连接的列放在
IS NULL
检查中(尽管 PK 或任何不可为空的列都可以,但当计划LEFT JOIN
使用非聚集索引时,它可能对效率很有用):还有第三种反连接方法,
NOT IN
但是如果内部表的列可以为空,则使用它具有不同的语义(和结果!)。它可以通过排除带有 的行来使用NULL
,使查询等效于前两个版本:这通常也会在大多数 DBMS 中产生类似的计划。
有一类情况是不可互换的——更具体地说,在这些情况下总是会接受一种情况,
SELECT 1
而SELECT *
另一种情况大多不会。我说的是您需要检查分组集的行是否存在的情况。如果表
T
有列C1
,C2
并且您正在检查是否存在与特定条件匹配的行组,则可以SELECT 1
这样使用:但你不能
SELECT *
以同样的方式使用。这只是一个句法方面。在语法上接受这两个选项的情况下,您很可能在性能或返回的结果方面没有差异,正如另一个答案中所解释的那样。
评论后的附加说明
似乎没有多少数据库产品实际上支持这种区别。SQL Server、Oracle、MySQL 和 SQLite 等产品很乐意接受
SELECT *
上述查询而不会出现任何错误,这可能意味着它们SELECT
以特殊方式处理 EXISTS。PostgreSQL 是一种
SELECT *
可能会失败的 RDBMS,但在某些情况下仍然可以工作。特别是,如果您按 PK 分组,SELECT *
则可以正常工作,否则将失败并显示以下消息:它们相同的“证明”(在 MySQL 中)是
然后用 重复
SELECT 1
。在这两种情况下,“扩展”输出都表明它已转换为SELECT 1
.同样,
COUNT(*)
变成COUNT(0)
.另一件需要注意的事情:在最近的版本中进行了优化改进。可能值得比较
EXISTS
与反连接。您的版本可能会比其他版本做得更好。至少在 SQL Server 中,重写
EXISTS
子句的一种可能很有趣的方式是:它的反半连接版本如下所示:
两者通常都针对与
WHERE EXISTS
or相同的计划进行优化WHERE NOT EXISTS
,但其意图是明确无误的,并且您没有“奇怪”的1
or*
。有趣的是,与 相关的空值检查问题对于来说
NOT IN (...)
是有问题的<> ALL (...)
,而NOT EXISTS (...)
不存在这个问题。考虑以下两个具有可为空列的表:我们将向两者添加一些数据,其中一些匹配的行,一些不匹配的行:
NOT IN (...)
查询:有以下计划:
该查询不返回任何行,因为 NULL 值使相等性无法确认。
此查询
<> ALL (...)
显示相同的计划并且不返回任何行:using 的变体
NOT EXISTS (...)
显示了稍微不同的计划形状,并且确实返回了行:计划:
该查询的结果:
这使得使用
<> ALL (...)
就像NOT IN (...)
.在某些数据库中,这种优化还不起作用。就像在 PostgreSQL中一样,从 9.6 版开始,这将失败。
这将成功。
它失败了,因为以下失败了,但这仍然意味着存在差异。
您可以在我对问题的回答中找到有关此特定怪癖和违反规范的更多信息,SQL 规范是否需要 EXISTS () 中的 GROUP BY
我一直使用
select top 1 'x'
(SQL Server)从理论上讲,
select top 1 'x'
这会更有效select *
,因为前者在选择存在合格行的常数后将是完整的,而后者将选择所有内容。然而,尽管在很早的时候它可能是相关的,但优化已经使可能所有主要的 RDBS 中的差异无关紧要。
IF EXISTS(SELECT TOP(1) 1 FROM
长期和跨平台是一个更好的习惯,因为您甚至不需要开始担心当前平台/版本的好坏;并且 SQL 正在从TOP n
参数化转向TOP(n)
。这应该是一次学习的技能。