- 文档
- 教程
- 知识库
【免费下载链接】til
:memo: Today I Learned
本指南以仓库文档 postgres/count-records-by-type.md 为核心,讲解如何利用
group by与聚合函数count(*)对带有类型列的数据库表进行分组统计。读完本文,你将掌握"按某列分类计数"这一最常用的数据探索手段,并进一步学会排序输出、处理 NULL 分组、按表达式分组、使用having过滤聚合结果等进阶写法,轻松应用于报表统计、去重排查与数据质量分析等场景。
一、核心问题:如何按类型统计记录数
在实际业务表中,我们经常会有某种"类型列"(type column)——例如订单表里的支付方式、日志表里的日志级别、商品表里的分类、宠物图鉴里的属性。最常见的需求是:这张表里每一种类型各有多少条记录?
答案非常简单:借助group by把记录按类型归组,再用聚合函数count(*)统计每一组的行数。原文档给出了一个经典的pokemon(宝可梦)示例:
> select type, count(*) from pokemon group by type; type | count ----------------- fire | 10 water | 4 plant | 7 psychic | 3 rock | 12这里group by type会将pokemon表中所有type相同的行合并为一个分组,随后count(*)对每个分组内的行数进行计数。最终结果每一行代表一种type及其对应的记录条数,是典型的一对多分组统计。
与之完全同构的另一个例子来自仓库文档 postgres/count-how-many-records-there-are-of-each-type.md:一张books表通过status列区分书籍处于published、review、draft等状态,同样一条查询即可得到各状态的书目数量:
> select status, count(*) from books group by status; status | count -----------+------- ø | 123 published | 611 draft | 364 review | 239 (4 rows)可以看到,两种写法完全一致:select 类型列, count(*) from 表名 group by 类型列就是"按类型统计记录数"的标准模板,可以套用在任何带有枚举、状态或分类性质的列上。
二、语法要点:SELECT 中的非聚合列必须出现在 GROUP BY 中
当select列表中同时出现普通列和聚合函数(如count(*)、sum、avg)时,PostgreSQL 要求:所有非聚合的普通列都必须出现在group by子句中,否则会直接报错。也就是说,select type, count(*) from pokemon group by type;中type既出现在投影列,又出现在分组键,二者保持一致,查询才能成立。
如果只写select type, count(*) from pokemon;(省略group by),PostgreSQL 会抛出错误提示:column "pokemon.type" must appear in the GROUP BY clause or be used in an aggregate function。这正是group by的语义保证——每一个输出行对应一个分组,而非对应单条记录。
三、进阶一:按计数降序排列,让结果更有秩序
默认情况下,group by输出的分组顺序并不保证稳定。仓库文档 postgres/count-how-many-records-there-are-of-each-type.md 指出,可以通过在查询末尾追加order by让输出按计数值排序,从而一眼看出数量最多的类型:
> select status, count(*) from books group by status order by 2 desc; status | count -----------+------- published | 611 draft | 364 review | 239 ø | 123 (4 rows)这里的order by 2用的是**位置索引(argument index)**写法。PostgreSQL 中select列表里的每个参数都拥有一个从 1 开始的索引,可以在order by和group by中直接引用。所以order by 2等价于order by count(*),desc表示从高到低降序排列。这种技巧在 postgres/use-argument-indexes.md 中有更完整的介绍——例如:
select id, updated_at from posts order by 2;就等价于order by updated_at。该文档还给出了一个同时使用位置索引完成"按类型分组 + 按计数降序"的组合示例:
select type, count(*) from transaction group by 1 order by 2 desc;其中group by 1引用type,order by 2引用count(*)。在列名较长或聚合表达式复杂时,位置索引能让查询更紧凑;不过若调整了select列顺序,位置索引会随之改变,需谨慎使用。
四、进阶二:NULL 类型也自成一组
当类型列没有not null约束时,group by会把所有NULL值的记录归入同一个分组。上面books示例输出中的ø一行就是status为 NULL 的记录组,共计 123 条。这是group by的既定行为:NULL 作为一个特殊的分组键存在。
这一点对实际业务很有意义——统计结果中出现的 NULL 分组,往往意味着数据录入不完整或存在脏数据,可以据此定位需要补全或清洗的记录,而不仅仅是把它们忽略掉。
五、进阶三:按函数/表达式结果分组,扩展统计维度
group by并不局限于直接按某一列分组。仓库文档 postgres/group-by-the-result-of-a-function-call.md 展示了按表达式结果分组的做法。假设products表的identifier列前三个字母代表商品分类,可以这样按分类统计:
select substring(identifier from 1 for 3), count(*) from products group by substring(identifier from 1 for 3);需要注意:group by中出现的表达式必须与select列表中的表达式逐字一致,PostgreSQL 不支持用别名(alias)引用分组表达式。这种写法把"按类型计数"推广到了"按类型的派生特征计数",例如按日期截断到月份统计订单数(group by date_trunc('month', created_at))、按首字母统计联系人数量等,非常适合数据探索。
六、进阶四:用 HAVING 过滤分组结果
如果你想要的不是全部分组,而是"计数满足某种条件的类型",则需要使用having子句。因为聚合函数(如count)不能在where中使用——where作用于分组之前的单行,而聚合结果要等分组完成后才产生。仓库文档 postgres/find-records-that-contain-duplicate-values.md 用它来查找重复邮箱:
select email, count(*) from mailing_list group by email having count(*) > 1 order by email;这个查询按email分组,having count(*) > 1仅保留出现次数大于 1 的分组,从而精确筛出重复记录。同样的思路可以套用到类型统计上:例如只想看记录数超过某个阈值的类型,只需把having条件改为having count(*) > 100。where过滤行,having过滤分组,这是组合使用时的关键区分。
七、进阶五:按布尔条件计数,统计分组内的满足数
除了统计"每一组有多少行",还常常需要统计"每一组内有多少行满足某个条件"。仓库文档 postgres/count-the-number-of-trues-in-an-aggregate-query.md 给出了用case表达式配合sum的经典写法:
select author_id, sum(case when available then 1 else 0 end) from books group by author_id;case在聚合前把布尔值转换为 1/0,sum累加后即为"可用书籍数"。统计不满足条件的数量时只需反转分支:
sum(case when available then 0 else 1 end)这种"按类型计数"的变体在权限统计、状态分布、指标卡占比等场景中极为实用,可以与group by自由组合成多维度的统计查询。
八、小结:按类型统计的查询范式
综合原文档 postgres/count-records-by-type.md 及其在仓库中的系列姊妹篇,可以归纳出按类型统计记录数的完整范式:
| 需求 | SQL 写法 | 关键点 |
|---|---|---|
| 每种类型各多少条 | select type, count(*) from t group by type; | 非聚合列必须在group by中 |
| 按计数从多到少排序 | 追加order by 2 desc(或order by count(*) desc) | 可用位置索引引用select参数 |
| NULL 类型也统计 | 无需额外处理 | NULL 自动成为独立分组 |
| 按派生特征统计 | group by substring(...)、group by date_trunc(...) | 表达式须在select与group by中一致 |
| 只看满足条件的分组 | 追加having count(*) > N | 聚合结果过滤必须用having |
| 统计组内满足条件的行 | sum(case when ... then 1 else 0 end) | case转 0/1 后聚合 |
掌握这一组写法后,任何带类型、状态、分类列的 PostgreSQL 表,都可以用几条简洁的 SQL 完成分布统计——这也是数据分析与报表开发中使用频率最高的基础能力之一。相关完整示例与进阶变体可继续在仓库 postgres/ 目录下查阅 count-how-many-records-there-are-of-each-type.md、use-argument-indexes.md 与 group-by-the-result-of-a-function-call.md 等文档。
- 文档
- 教程
- 知识库
【免费下载链接】til
:memo: Today I Learned
相关推荐
Offer Formats by Business Type:按业务类型选对报价结构的实战指南(marketingskills 开源仓库解析)
Offer Formats by Business Type:按业务类型选对报价结构的实战指南(marketingskills 开源仓库解析) 导读 同样的六个
AI 技能人工智能最实用的LitePal聚合查询指南:从GROUP BY到复杂统计分析
最实用的LitePal聚合查询指南:从GROUP BY到复杂统计分析 你是否还在为Android项目中的数据统计功能编写大量SQL语句?是否遇到过需要按类别统计
数据库ORM移动开发Open-Meteo:零密钥的免费天气 API,一条 curl 拿全球预报
Open Meteo:零密钥的免费天气 API,一条 curl 拿全球预报 Open Meteo 是一个用 Swift 编写的开源气象数据服务:它把 NOAA
后端API网关数据工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考