在 SQL 中,我是否应该总是更喜欢 EXISTS 而不是 COUNT() > 0?
Should I always prefer EXISTS over COUNT() > 0 in SQL?
我经常遇到这样的建议,即在检查(子)查询中的任何行是否存在时,出于性能原因,应该使用 EXISTS
而不是 COUNT(*) > 0
。具体来说,前者可以在找到单行后短路returnTRUE
(或者NOT EXISTS
的FALSE
),而COUNT
需要实际上评估每一行以 return 一个数字,仅与零进行比较。
在简单的情况下,这一切对我来说都很有意义。但是,我最近 运行 遇到一个问题,我需要根据组中某一列中的所有值是否为 [=22] 来过滤 GROUP BY
的 HAVING
子句中的组=].
为了清楚起见,让我们看一个例子。假设我有以下架构:
CREATE TABLE profile(
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
google_account_id INTEGER NULL,
facebook_account_id INTEGER NULL,
FOREIGN KEY (user_id) REFERENCES user(id),
CHECK(
(google_account_id IS NOT NULL) + (facebook_account_id IS NOT NULL) = 1
)
)
即每个用户(table 为简洁起见未显示)有 0 个或多个配置文件。每个个人资料都是 Google 或 Facebook 帐户。 (这是子类的 t运行slation 或具有一些关联数据的总和类型——在我的真实模式中,帐户 ID 也是持有该关联数据的不同 tables 的外键,但这是与我的问题无关。)
现在,假设我想计算所有 没有任何 Google 个人资料的用户的 Facebook 个人资料。
起初,我使用 COUNT() = 0
编写了以下查询:
SELECT user_id, COUNT(facebook_account_id)
FROM profile
GROUP BY user_id
HAVING COUNT(google_account_id) = 0;
但后来我想到 HAVING
子句中的条件实际上只是一个存在性检查。所以我然后使用子查询和 NOT EXISTS
:
重新编写了查询
SELECT user_id, COUNT(facebook_account_id)
FROM profile AS p
GROUP BY user_id
HAVING NOT EXISTS (
SELECT 1
FROM profile AS q
WHERE p.user_id = q.user_id
AND q.google_id IS NOT NULL
)
我的问题有两个:
我是否应该保留第二个重新制定的查询,并在子查询中使用 NOT EXISTS
而不是 COUNT() = 0
?这真的更有效率吗?我认为由于 WHERE p.user_id = q.user_id
条件导致的索引查找有一些额外的成本。这种额外成本是否被 EXISTS
的短路行为吸收也可能取决于组的平均基数,不是吗?
或者 DBMS 是否可以足够聪明地识别分组键被比较的事实,并通过用当前组替换它来完全优化这个子查询(而不是为每个子查询实际执行索引查找)团体)?我严重怀疑 DBMS 是否可以优化这个子查询,同时未能将 COUNT() = 0
优化为 NOT EXISTS
.
撇开效率不谈,第二个查询对我来说似乎更复杂,而且不太正确,所以即使它更快,我也不愿意使用它。你怎么看,还有更好的方法吗?通过以更简单的方式使用 NOT EXISTS
,例如通过直接从 HAVING 子句中引用当前组,我可以吃蛋糕吗?
第一个查询似乎是执行所需操作的正确方法。
这已经是聚合查询了,因为您要统计 Facebook 帐户。处理计算 google 个帐户的 having
子句的开销应该很小。
另一方面,第二种方法需要重新打开 table 并扫描它,这很可能更昂贵。
在 子查询 中,您应该更喜欢 EXISTS
/NOT EXISTS
而不是 COUNT()
。所以而不是:
select t.*
from t
where (select count(*) from z where z.x = t.x) > 0
您应该改用:
select t.*
from t
where exists (select 1 from z where z.x = t.x)
这样做的原因是子查询可以在第一次匹配时停止处理。
此推理不适用于聚合后的 HAVING
子句——无论如何都必须生成所有行,因此在第一次匹配时停止没有什么价值。
但是,如果您有 users
table 并且真的不需要 facebook 计数,则可能不需要聚合。您可以使用:
select u.*
from users u
where not exists (select 1
from profiles p
where p.user_id = u.user_id and p.google_id is not null
);
此外,如果您在聚合之前过滤,聚合可能会更快:
SELECT user_id, COUNT(facebook_account_id)
FROM profile AS p
WHERE NOT EXISTS (
SELECT 1
FROM profile p2
WHERE p2.user_id = p.user_id AND p2.google_id IS NOT NULL
)
GROUP BY user_id;
它是否真的更快取决于很多因素,包括实际过滤掉的行数。
我经常遇到这样的建议,即在检查(子)查询中的任何行是否存在时,出于性能原因,应该使用 EXISTS
而不是 COUNT(*) > 0
。具体来说,前者可以在找到单行后短路returnTRUE
(或者NOT EXISTS
的FALSE
),而COUNT
需要实际上评估每一行以 return 一个数字,仅与零进行比较。
在简单的情况下,这一切对我来说都很有意义。但是,我最近 运行 遇到一个问题,我需要根据组中某一列中的所有值是否为 [=22] 来过滤 GROUP BY
的 HAVING
子句中的组=].
为了清楚起见,让我们看一个例子。假设我有以下架构:
CREATE TABLE profile(
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
google_account_id INTEGER NULL,
facebook_account_id INTEGER NULL,
FOREIGN KEY (user_id) REFERENCES user(id),
CHECK(
(google_account_id IS NOT NULL) + (facebook_account_id IS NOT NULL) = 1
)
)
即每个用户(table 为简洁起见未显示)有 0 个或多个配置文件。每个个人资料都是 Google 或 Facebook 帐户。 (这是子类的 t运行slation 或具有一些关联数据的总和类型——在我的真实模式中,帐户 ID 也是持有该关联数据的不同 tables 的外键,但这是与我的问题无关。)
现在,假设我想计算所有 没有任何 Google 个人资料的用户的 Facebook 个人资料。
起初,我使用 COUNT() = 0
编写了以下查询:
SELECT user_id, COUNT(facebook_account_id)
FROM profile
GROUP BY user_id
HAVING COUNT(google_account_id) = 0;
但后来我想到 HAVING
子句中的条件实际上只是一个存在性检查。所以我然后使用子查询和 NOT EXISTS
:
SELECT user_id, COUNT(facebook_account_id)
FROM profile AS p
GROUP BY user_id
HAVING NOT EXISTS (
SELECT 1
FROM profile AS q
WHERE p.user_id = q.user_id
AND q.google_id IS NOT NULL
)
我的问题有两个:
我是否应该保留第二个重新制定的查询,并在子查询中使用
NOT EXISTS
而不是COUNT() = 0
?这真的更有效率吗?我认为由于WHERE p.user_id = q.user_id
条件导致的索引查找有一些额外的成本。这种额外成本是否被EXISTS
的短路行为吸收也可能取决于组的平均基数,不是吗?或者 DBMS 是否可以足够聪明地识别分组键被比较的事实,并通过用当前组替换它来完全优化这个子查询(而不是为每个子查询实际执行索引查找)团体)?我严重怀疑 DBMS 是否可以优化这个子查询,同时未能将
COUNT() = 0
优化为NOT EXISTS
.撇开效率不谈,第二个查询对我来说似乎更复杂,而且不太正确,所以即使它更快,我也不愿意使用它。你怎么看,还有更好的方法吗?通过以更简单的方式使用
NOT EXISTS
,例如通过直接从 HAVING 子句中引用当前组,我可以吃蛋糕吗?
第一个查询似乎是执行所需操作的正确方法。
这已经是聚合查询了,因为您要统计 Facebook 帐户。处理计算 google 个帐户的 having
子句的开销应该很小。
另一方面,第二种方法需要重新打开 table 并扫描它,这很可能更昂贵。
在 子查询 中,您应该更喜欢 EXISTS
/NOT EXISTS
而不是 COUNT()
。所以而不是:
select t.*
from t
where (select count(*) from z where z.x = t.x) > 0
您应该改用:
select t.*
from t
where exists (select 1 from z where z.x = t.x)
这样做的原因是子查询可以在第一次匹配时停止处理。
此推理不适用于聚合后的 HAVING
子句——无论如何都必须生成所有行,因此在第一次匹配时停止没有什么价值。
但是,如果您有 users
table 并且真的不需要 facebook 计数,则可能不需要聚合。您可以使用:
select u.*
from users u
where not exists (select 1
from profiles p
where p.user_id = u.user_id and p.google_id is not null
);
此外,如果您在聚合之前过滤,聚合可能会更快:
SELECT user_id, COUNT(facebook_account_id)
FROM profile AS p
WHERE NOT EXISTS (
SELECT 1
FROM profile p2
WHERE p2.user_id = p.user_id AND p2.google_id IS NOT NULL
)
GROUP BY user_id;
它是否真的更快取决于很多因素,包括实际过滤掉的行数。