存在(选择1…)vs存在(选择*…)一个或另一个?


37

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

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:在某些情况下,我可能会使用LEFT 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,或者当您的主键或联接条件为多列时(您很容易忘记其中一列)。但是,有时您需要维护由其他人编写的代码...而它就在那里。

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

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

  3. 显式编写反连接是否有任何优势?
    当代的计划者/优化者是否将其与NOT EXISTS条款区别对待?


5
请注意,PostgreSQL支持没有列的选择,因此您只需编写即可EXISTS (SELECT FROM ...)
2016年

1
我一直在问几乎所以同样的问题在几年前:stackoverflow.com/questions/7710153/...
欧文Brandstetter修改

Answers:


45

不,还有介于两者之间的效率没有差异(NOT) EXISTS (SELECT 1 ...),并(NOT) EXISTS (SELECT * ...)在所有主要的DBMS。我也经常看到(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)的优化器或多或少地具有重写这两种方法并为两种方法考虑相同计划的能力。尽管如此,仍然没有这样的保证,并且在进行优化时,最好检查来自不同等效重写的计划,因为在某些情况下,每个优化器都不会重写(例如,复杂的查询,具有许多联接和/或派生表/子查询中的子查询,其中来自多个表的条件,联接条件中使用的复合列)或优化器的选择和计划受可用索引,设置等不同地影响。

还要注意,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 ;

还有另一种用于反连接的方法,使用,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
       ) ;

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


1
直到最近才MySQL版本,[NOT] IN (SELECT ...)虽然等同,执行糟糕。避开它!
瑞克·詹姆斯

3
PostgreSQL并非如此SELECT *当然可以做更多的工作。为了简单起见,我建议您使用SELECT 1
Evan Carroll

11

有一种情况的情况下的一个类别SELECT 1,并SELECT *不能互换-更具体地说,一个总是会在这些情况下,而其他大多不会接受。

我说的是需要检查分组集行是否存在的情况。如果表中T有列C1C2并且您要检查是否存在符合特定条件的行组,则可以这样使用SELECT 1

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)

但是您不能SELECT *以相同的方式使用。

那仅仅是一个句法方面。在语法上同时接受两个选项的情况下,您在性能或返回结果方面极有可能没有差异,如其他答案所述

评论后的附加说明

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

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

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


1
好点,尽管我担心的并非如此。这显示出概念上的差异。因为,当您使用时GROUP BY,的概念*毫无意义(或者至少不清楚)。
joanolo

5

EXISTS至少在SQL Server中,重写该子句可以使查询更整洁,并且可能减少误导的一种有趣的方式可能是:

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );

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

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );

这两种通常被优化到相同的计划,WHERE EXISTSWHERE NOT EXISTS,但我们的目的是明确无误的,你有没有“怪” 1*

有趣的是,与关联的null检查问题对于来说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
);

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

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;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +
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;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
| 4 | 4 |
+ -------- + ----------- +

NOT IN (...)查询:

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );

有以下计划:

在此处输入图片说明

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

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

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );

在此处输入图片说明

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

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );

计划:

在此处输入图片说明

该查询的结果:

+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +

这使得使用<> ALL (...)和一样容易出现有问题的结果NOT IN (...)


3
我必须说我并不*奇怪:我读EXISTS (SELECT * FROM t WHERE ...) AS there is a _row_ in table _t_ that...。无论如何,我喜欢替代品,并且您的可读性很强。一个疑问/观点:如果b可为空,它将如何表现?[我试图找出造成了一个misstake时有不好的经验和一些短夜x IN (SELECT something_nullable FROM a_table)]
joanolo

EXISTS告诉您表是否具有行并返回true或false。EXISTS(SELECT x FROM(values(null))是true。IN = ANY&NOT IN <> ALL。这4个RHS行带有NULL可能匹配。(x)= ANY(values(null))& (x)<> ALL(值(空))是未知/空,但EXISTS(值(空))是真的。(IN&= ANY具有与“ NOT IN(...)相关的空检查问题” ] <> ALL(...)”。任何&ALL都会进行OR和AND的迭代。但是,如果您没有按预期的方式组织语义,那么只会存在“问题”。)不要建议将它们用于EXISTS。 ,而不是“减少误导”
philipxy

@philliprxy-如果我错了,我就可以接受。如果您愿意,可以随意添加自己的答案。
Max Vernon

4

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

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;

然后重复SELECT 1。在这两种情况下,“扩展”输出都将其转换为SELECT 1

同样,COUNT(*)变成COUNT(0)

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


4

在某些数据库中,此优化尚不起作用。像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
);

这将成功。

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
);

之所以失败,是因为以下失败,但这仍然意味着有所不同。

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;

您可以在我对以下问题的回答中找到有关此特殊古怪和违反规范的更多信息:SQL规范是否要求EXISTS()中的GROUP BY


一个罕见的极端情况,有点怪异的可能,但再一次,一个证明,你必须做出很多妥协的设计数据库的时候...
joanolo

-1

我一直用select top 1 'x'(SQL Server)

从理论上讲,select top 1 'x'将比效率更高select *,因为前者将在存在限定行的情况下选择一个常数后完成,而后者将选择所有内容。

但是,尽管可能在很早的时候就已经涉及到了,但优化可能使所有主要RDBS的差异变得无关紧要。


说得通。那可能是(或者曾经是)极少数top n没有order by好主意的情况之一。
joanolo

3
“理论上,....”不,理论上select top 1 'x'应不大于更有效率select *Exist表达。实际上,如果优化程序的工作不是最理想的,则效率可能更高,但理论上两个表达式都是等效的。
miracle173

-4

IF EXISTS(SELECT TOP(1) 1 FROM从长远来看,跨平台是一个更好的习惯,因为您甚至不必担心当前平台/版本的优劣;SQL正在从可TOP n参数化转向TOP(n)。这应该是一次学习的技能。


3
“跨平台”是什么意思?TOP甚至不是有效的SQL。
ypercubeᵀᴹ

“ SQL正在移动..”是完全错误的。TOP (n)标准查询语言“ SQL”中没有。T-SQL上有一个是Microsoft SQL Server使用的方言。
a_horse_with_no_name

原始问题上的标签是“ SQL Server”。但是,可以对我所说的内容进行否决并提出异议,这是可以的-这是本网站旨在简化表决的目的。我是谁会无聊地关注细节而在您的游行中下雨?
ajeh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.