COUNT(*) vs. COUNT(1) vs. COUNT(pk):哪个更好? [复制]
Posted
技术标签:
【中文标题】COUNT(*) vs. COUNT(1) vs. COUNT(pk):哪个更好? [复制]【英文标题】:COUNT(*) vs. COUNT(1) vs. COUNT(pk): which is better? [duplicate] 【发布时间】:2011-02-12 05:29:35 【问题描述】:我经常发现这三种变体:
SELECT COUNT(*) FROM Foo;
SELECT COUNT(1) FROM Foo;
SELECT COUNT(PrimaryKey) FROM Foo;
据我所知,它们都做同样的事情,我发现自己在我的代码库中使用了这三个。但是,我不喜欢以不同的方式做同样的事情。我应该坚持哪一个?他们中的任何一个比其他两个更好吗?
【问题讨论】:
+1,我什至不知道,SELECT COUNT(PrimaryKey) FROM Foo;
甚至是一个选项
IMO,如果您不知道其中的区别,请选择一个并坚持下去。如果你不可能是对的,至少要保持一致。
@Anthony Forloney:让我们明确一点,PrimaryKey
指的是您的主键字段的名称,它不是什么神奇的关键字。
@zneak,是的,我意识到当 mysql 向我抛出一个错误时 Unknown column "primarykey" in 'field list' 干得好。
@gbn:是的,它可能是重复的。但不是完全重复,OP 考虑了 COUNT(PrimaryKey) 构造。所以这使它不完全重复。这是一个独立的主题,与其他两种方法形成对比
【参考方案1】:
底线
使用COUNT(field)
或COUNT(*)
,并坚持使用它,如果您的数据库允许COUNT(tableHere)
或COUNT(tableHere.*)
,请使用它。
简而言之,不要将COUNT(1)
用于任何事情。它是一招一式的小马,很少做你想做的事,在极少数情况下相当于count(*)
使用count(*)
进行计数
对所有需要计算所有内容的查询使用*
,即使对于连接,使用*
SELECT boss.boss_id, COUNT(subordinate.*)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
但不要将COUNT(*)
用于左连接,因为即使从属表与父表中的任何内容都不匹配,它也会返回 1
SELECT boss.boss_id, COUNT(*)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
不要被那些建议在 COUNT 中使用 *
时,它会从您的表中获取整行,说 *
很慢的人所迷惑。 SELECT COUNT(*)
和 SELECT *
上的 *
彼此无关,它们是完全不同的东西,它们只是共享一个共同的标记,即 *
。
另一种语法
事实上,如果不允许将字段命名为与其表名相同,RDBMS 语言设计器可以赋予COUNT(tableNameHere)
与COUNT(*)
相同的语义。示例:
为了计算行数,我们可以这样:
SELECT COUNT(emp) FROM emp
他们可以让它变得更简单:
SELECT COUNT() FROM emp
对于 LEFT JOIN,我们可以这样:
SELECT boss.boss_id, COUNT(subordinate)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
但他们不能这样做 (COUNT(tableNameHere)
),因为 SQL 标准允许使用与其表名相同的名称来命名字段:
CREATE TABLE fruit -- ORM-friendly name
(
fruit_id int NOT NULL,
fruit varchar(50), /* same name as table name,
and let's say, someone forgot to put NOT NULL */
shape varchar(50) NOT NULL,
color varchar(50) NOT NULL
)
用 null 计数
此外,如果字段名称与表名称匹配,则将字段设为可空也不是一个好习惯。假设您在 fruit
字段上有值 'Banana'、'Apple'、NULL、'Pears'。这不会计算所有行,它只会产生 3,而不是 4
SELECT count(fruit) FROM fruit
虽然一些 RDBMS 采用这种原则(为了计算表的行数,它接受表名作为 COUNT 的参数),但这将在 Postgresql 中工作(如果下面两个表中的任何一个中都没有 subordinate
字段,即只要字段名和表名没有名字冲突):
SELECT boss.boss_id, COUNT(subordinate)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
但是,如果我们在表中添加 subordinate
字段,以后可能会造成混淆,因为它将计算字段(可以为空),而不是表行。
为了安全起见,请使用:
SELECT boss.boss_id, COUNT(subordinate.*)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
count(1)
: 一招制胜的小马
特别是COUNT(1)
,它是一个一招制胜的小马,它只适用于一个表查询:
SELECT COUNT(1) FROM tbl
但是当您使用连接时,该技巧不适用于多表查询而不会混淆其语义,尤其是您不能编写:
-- count the subordinates that belongs to boss
SELECT boss.boss_id, COUNT(subordinate.1)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
那么这里的 COUNT(1) 是什么意思呢?
SELECT boss.boss_id, COUNT(1)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
这是……吗?
-- counting all the subordinates only
SELECT boss.boss_id, COUNT(subordinate.boss_id)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
还是这个……?
-- or is that COUNT(1) will also count 1 for boss regardless if boss has a subordinate
SELECT boss.boss_id, COUNT(*)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
仔细想一想,你可以推断出COUNT(1)
和COUNT(*)
是一样的,不管是什么类型的join。但是对于 LEFT JOIN 的结果,我们不能将 COUNT(1)
塑造成:COUNT(subordinate.boss_id)
, COUNT(subordinate.*)
所以只需使用以下任一方法:
-- count the subordinates that belongs to boss
SELECT boss.boss_id, COUNT(subordinate.boss_id)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
在 Postgresql 上工作,很明显你想计算集合的基数
-- count the subordinates that belongs to boss
SELECT boss.boss_id, COUNT(subordinate.*)
FROM boss
LEFT JOIN subordinate on subordinate.boss_id = boss.boss_id
GROUP BY boss.id
另一种计算集合基数的方法,非常类似于英语(只是不要创建名称与其表名相同的列):http://www.sqlfiddle.com/#!1/98515/7
select boss.boss_name, count(subordinate)
from boss
left join subordinate on subordinate.boss_code = boss.boss_code
group by boss.boss_name
你不能这样做:http://www.sqlfiddle.com/#!1/98515/8
select boss.boss_name, count(subordinate.1)
from boss
left join subordinate on subordinate.boss_code = boss.boss_code
group by boss.boss_name
你可以这样做,但这会产生错误的结果:http://www.sqlfiddle.com/#!1/98515/9
select boss.boss_name, count(1)
from boss
left join subordinate on subordinate.boss_code = boss.boss_code
group by boss.boss_name
【讨论】:
COUNT(1) 看起来像一个神奇的数字,当有人已经掌握了幕后发生的事情时使用这个数字。它可能导致滥用(即如果有恶意),因为所有 COUNT(0)、COUNT(1)、COUNT(2)、COUNT(42)(你明白了)都与 COUNT(@987654373 @),例如,有人可能会混淆代码并使用 COUNT(2),因此下一个维护者可能很难推断出这些 COUNT 的作用。只有当他/她已经收集到 COUNT(1) 与 COUNT(*
) 相同时,才会开始使用 COUNT(1)。没有人从 COUNT(1) 开始他们的数据库生涯
或者来自 jester 程序员,他们可以这样做:SELECT COUNT('ME IN') FROM tbl
,因为认为像 COUNT(1)
中的 1 一样,'ME IN' 也会被 RDBMS 忽略和优化
当然它“有效”,问题是它正确有效吗?如果 John 有两个下属 George 和 Ringo,而 Paul 没有,请尝试将 COUNT(1)
设置为 LEFT JOIN
以使其正常工作,因此 Paul 的下属数量将为 0。首先解决此问题:sqlfiddle.com/#!1/98515/13跨度>
我在关于在LEFT JOIN
上使用COUNT(1)
的回答中强调了这一声明:您可以这样做,但这会产生错误的结果。在此页面上搜索此短语:错误结果
@MichaelBuen 信息量很大!但是你似乎总是把你最有说服力的论点放在一段文字的底部。我试图将其更改为以下模式:(1)有争议的断言以吸引注意力,(2)用事实和例子来支持它。语法部分本身很有趣,但几乎与要点无关。我会把它移到底部,但我不能没有大的重写。再次,非常有用,谢谢!【参考方案2】:
其中两个总是产生相同的答案:
COUNT(*)
统计行数
COUNT(1)
也会统计行数
假设pk
是一个主键并且值中不允许有空值,那么
COUNT(pk)
也会统计行数
但是,如果pk
不被限制为不为空,那么它会产生不同的答案:
COUNT(possibly_null)
统计possibly_null
列中非空值的行数。
COUNT(DISTINCT pk)
还计算行数(因为主键不允许重复)。
COUNT(DISTINCT possibly_null_or_dup)
计算possibly_null_or_dup
列中不同的非空值的数量。
COUNT(DISTINCT possibly_duplicated)
计算列possibly_duplicated
中包含NOT NULL
子句的不同(必须非空)值的数量。
通常,我写COUNT(*)
;它是最初推荐的 SQL 表示法。同样,对于EXISTS
子句,我通常写WHERE EXISTS(SELECT * FROM ...)
,因为这是最初的推荐符号。替代品应该没有任何好处;优化器应该看穿更晦涩的符号。
【讨论】:
我什至不知道COUNT(DISTINCT)
工作,尽管它是有道理的。它是特定于 SQL 风格的,还是受到广泛支持?
@zneak: COUNT(DISTINCT x) 自 SQL-86(第一个标准)以来一直在 SQL 中,所以我会惊讶地发现任何不支持它的 SQL DBMS。【参考方案3】:
Asked and answered before...
Books on line 说“COUNT ( [ [ ALL | DISTINCT ] expression ] | * )
”
“1”是一个非空表达式,因此它与COUNT(*)
相同。
优化器将其识别为微不足道,因此给出了相同的计划。 PK 是唯一且非空的(至少在 SQL Server 中)所以 COUNT(PK)
= COUNT(*)
这与EXISTS (SELECT * ...
或EXISTS (SELECT 1 ...
类似
参见ANSI 92 spec,第 6.5 节,一般规则,案例 1
a) If COUNT(*) is specified, then the result is the cardinality
of T.
b) Otherwise, let TX be the single-column table that is the
result of applying the <value expression> to each row of T
and eliminating null values. If one or more null values are
eliminated, then a completion condition is raised: warning-
null value eliminated in set function.
【讨论】:
【参考方案4】:至少在 Oracle 上它们都是一样的:http://www.oracledba.co.uk/tips/count_speed.htm
【讨论】:
【参考方案5】:我觉得从一个 DBMS 到另一个的性能特征发生了变化。这完全取决于他们如何选择实施它。由于我在 Oracle 上工作过很多次,所以我会从这个角度来说明。
COUNT(*)
- 在传递给 count 函数之前将整行获取到结果集中,如果该行不为 null,则 count 函数将聚合 1
COUNT(1)
- 不会获取任何行,而是在 WHERE
匹配时为表中的每一行调用一个常量值 1 的计数。
COUNT(PK)
- Oracle 中的 PK 已编入索引。这意味着 Oracle 必须只读取索引。通常索引 B+ 树中的一行比实际行小很多倍。因此,考虑到磁盘 IOPS 速率,与整行相比,Oracle 通过单个块传输从索引中获取的行数是整行的数倍。这会提高查询的吞吐量。
由此可以看出,Oracle 中第一个计数最慢,最后一个计数最快。
【讨论】:
幸运的是,在你离开后他们已经足够明智地改变了这一点 - oracledba.co.uk/tips/count_speed.htm以上是关于COUNT(*) vs. COUNT(1) vs. COUNT(pk):哪个更好? [复制]的主要内容,如果未能解决你的问题,请参考以下文章