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):哪个更好? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

mysql count(*) vs count

Length vs COUNT

UPDATE vs COUNT vs SELECT 性能

列表:Count vs Count() [重复]

Android ListView.count vs ListView.size

Swift array.capacity vs array.count