EXISTS (SELECT 1 ...) vs EXISTS (SELECT * ...) 一个还是另一个?

joa*_*olo 47 mysql postgresql oracle sql-server

每当我需要检查表中某行是否存在时,我总是倾向于编写如下条件:

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
       )
Run Code Online (Sandbox Code Playgroud)

还有一些人这样写:

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
       )
Run Code Online (Sandbox Code Playgroud)

当条件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
Run Code Online (Sandbox Code Playgroud)

我尽量避免它,因为我认为含义不太清楚,特别是当你primary_key的不是那么明显时,或者当你的主键或你的连接条件是多列时(并且你很容易忘记其中一列)。但是,有时您会维护由其他人编写的代码......而且它就在那里。

  1. 使用SELECT 1代替有什么区别(样式除外)SELECT *吗?
    是否有任何极端情况下它的行为方式不同?

  2. 虽然我写的是(AFAIK)标准SQL:不同的数据库/旧版本有这样的区别吗?

  3. 显式编写反连接有什么好处吗?
    当代的规划者/优化者对它的处理方式与NOT EXISTS条款不同吗?

ype*_*eᵀᴹ 53

不,在所有主要 DBMS之间(NOT) EXISTS (SELECT 1 ...)(NOT) EXISTS (SELECT * ...)在效率上没有区别。我也经常看到(NOT) EXISTS (SELECT NULL ...)被使用。

在某些情况下,您甚至可以编写(NOT) EXISTS (SELECT 1/0 ...)并且结果是相同的 - 没有任何(被零除)错误,这证明那里的表达式甚至没有被评估。


关于LEFT JOIN / IS NULLantijoin 方法,更正:这相当于NOT EXISTS (SELECT ...).

在这种情况下,NOT EXISTSvsLEFT JOIN / IS NULL,你可能会得到不同的执行计划。例如,在 MySQL 和大多数旧版本(5.7 之前)中,计划将非常相似但不完全相同。其他 DBMS(SQL Server、Oracle、Postgres、DB2)的优化器 - 据我所知 - 或多或少能够重写这 2 种方法并为两者考虑相同的计划。尽管如此,仍然没有这样的保证,并且在进行优化时,最好检查来自不同等效重写的计划,因为可能存在每个优化器不重写的情况(例如,具有许多连接和/或派生表的复杂查询/子查询中的子查询,其中来自多个表的条件、连接条件中使用的复合列)或优化器选择和计划受到可用索引、设置等的不同影响。

另请注意,USING不能在所有 DBMS(例如 SQL Server)中使用。JOIN ... ON到处都是比较常见的作品。
并且列需要以表名/别名作为前缀,SELECT以避免在我们进行连接时出现错误/歧义。
我通常也更喜欢将连接的列放在IS NULL检查中(尽管 PK 或任何不可为空的列都可以,但当计划LEFT JOIN使用非聚集索引时,它可能对提高效率很有用):

SELECT a_table.a, a_table.b, a_table.c
  FROM a_table
       LEFT JOIN another_table 
           ON another_table.b = a_table.b
 WHERE another_table.b IS NULL ;
Run Code Online (Sandbox Code Playgroud)

反连接还有第三种方法,NOT IN如果内部表的列可以为空,则使用它具有不同的语义(和结果!)。可以通过使用 排除行来使用它NULL,使查询等效于前两个版本:

SELECT a, b, c
  FROM a_table
 WHERE a_table.b NOT IN 
       (SELECT another_table.b
          FROM another_table
         WHERE another_table.b IS NOT NULL
       ) ;
Run Code Online (Sandbox Code Playgroud)

这通常也会在大多数 DBMS 中产生类似的计划。

  • [PostgreSQL 不是这样](http://dba.stackexchange.com/q/168047/2639)。`SELECT *` 肯定做了更多的工作。为简单起见,我建议使用`SELECT 1` (5认同)
  • 另外,值得注意的是,“SELECT 1”还有另一个优点,至少在 PostgreSQL 中是这样 - 当您在视图中执行“SELECT * FROM foo”时,如果不先删除视图,则无法删除“foo”表中的任何列,如果您使用“SELECT 1”,则情况并非如此。 (2认同)

And*_*y M 12

有一类情况SELECT 1SELECT *不能互换 - 更具体地说,在这些情况下总是会接受一种情况,而另一种情况则不会。

我说的是需要检查分组集的行是否存在的情况。如果表T有列C1并且C2您正在检查是否存在与特定条件匹配的行组,则可以SELECT 1像这样使用:

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)
Run Code Online (Sandbox Code Playgroud)

但你不能SELECT *以同样的方式使用。

这只是一个语法方面。在语法上接受这两个选项的情况下,您很可能在性能或返回的结果方面没有区别,如其他答案中所述

评论后的附加说明

似乎没有多少数据库产品实际上支持这种区别。SQL Server、Oracle、MySQL 和 SQLite 等产品会很乐意接受SELECT *上述查询而不会出现任何错误,这可能意味着它们SELECT以特殊方式对待 EXISTS 。

PostgreSQL 是一种SELECT *可能会失败但在某些情况下仍然可以工作的RDBMS 。特别是,如果您按 PK 分组,则SELECT *可以正常工作,否则将失败并显示以下消息:

错误:列“T.C2”必须出现在 GROUP BY 子句中或用于聚合函数中


Ric*_*mes 5

证明它们相同(在 MySQL 中)的“证明”是

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;
Run Code Online (Sandbox Code Playgroud)

然后重复SELECT 1。在这两种情况下,“扩展”输出显示它已转换为SELECT 1.

同理,COUNT(*)变成了COUNT(0)

另一件需要注意的事情:在最近的版本中进行了优化改进。EXISTS与反连接进行比较可能是值得的。您的版本可能在其中一个方面做得更好。


Han*_*non 5

EXISTS至少在 SQL Server 中,重写子句的一种可以说是有趣的方法是:

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );
Run Code Online (Sandbox Code Playgroud)

其反半连接版本如下所示:

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );
Run Code Online (Sandbox Code Playgroud)

两者通常都针对与WHERE EXISTS或相同的计划进行优化WHERE NOT EXISTS,但意图是明确无误的,并且您没有“奇怪的”1*

有趣的是,与 相关的空检查问题对于NOT IN (...)是有问题的<> ALL (...),而NOT EXISTS (...)则不会遇到这个问题。考虑以下两个带有可为空列的表:

IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
    DROP TABLE #t;
END;
CREATE TABLE #t 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
    DROP TABLE #s;
END;
CREATE TABLE #s 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);
Run Code Online (Sandbox Code Playgroud)

我们将向两者添加一些数据,其中一些行匹配,一些不匹配:

INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);

SELECT *
FROM #t;
Run Code Online (Sandbox Code Playgroud)
+--------+-----------+
| 身份证 | 一些价值 |
+--------+-----------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 空 |
+--------+-----------+
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);

SELECT *
FROM #s;
Run Code Online (Sandbox Code Playgroud)
+--------+-----------+
| 身份证 | 一些价值 |
+--------+-----------+
| 1 | 1 |
| 2 | 2 |
| 3 | 空 |
| 4 | 4 |
+--------+-----------+

NOT IN (...)查询:

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );
Run Code Online (Sandbox Code Playgroud)

有以下计划:

在此处输入图片说明

查询不返回任何行,因为 NULL 值使得无法确认相等性。

此查询<> ALL (...)显示相同的计划并且不返回任何行:

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

使用NOT EXISTS (...),的变体显示略有不同的计划形状,并且确实返回行:

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );
Run Code Online (Sandbox Code Playgroud)

计划:

在此处输入图片说明

该查询的结果:

+--------+-----------+
| 身份证 | 一些价值 |
+--------+-----------+
| 3 | 3 |
| 4 | 空 |
+--------+-----------+

这使得 using<> ALL (...)NOT IN (...).


Eva*_*oll 5

在某些数据库中,这种优化还不起作用。例如在 PostgreSQL 中,从 9.6 版开始,这将失败。

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT *
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);
Run Code Online (Sandbox Code Playgroud)

而这会成功。

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT 1  -- This changed from the first query
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);
Run Code Online (Sandbox Code Playgroud)

它失败了,因为以下失败了,但这仍然意味着存在差异。

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;
Run Code Online (Sandbox Code Playgroud)

您可以在我对问题的回答中找到有关此特定怪癖和违反规范的更多信息,SQL 规范是否需要在 EXISTS () 中使用 GROUP BY