Appearance
05|查询进阶:连接、聚合、函数
上一篇的查询都是单表——SELECT ... FROM servers WHERE ...。但真实业务里,数据分散在多张表:服务器在一张表、负责人在另一张表、监控指标又在别的表。查"这台服务器是谁负责的",得把两张表关联起来。
本篇讲查询进阶:JOIN 连接多表、GROUP BY 分组统计、常用函数、子查询。
一、JOIN:关联多张表
准备两张表。servers 记服务器,owners 记负责人:
sql
-- servers 表
id | hostname | owner_id | env
1 | web-01 | 100 | prod
2 | web-02 | 101 | prod
-- owners 表
owner_id | name | email
100 | 张三 | zhangsan@xx
101 | 李四 | lisi@xxservers.owner_id 指向 owners.owner_id。查"每台服务器是谁负责的":
sql
SELECT s.hostname, o.name AS owner, o.email
FROM servers s
JOIN owners o ON s.owner_id = o.owner_id;text
hostname | owner | email
web-01 | 张三 | zhangsan@xx
web-02 | 李四 | lisi@xxs 和 o 是表的别名(FROM servers s),写起来短。JOIN ... ON 指定怎么关联——这里用 owner_id 相等。
三种 JOIN 的区别
sql
-- INNER JOIN(默认 JOIN):只返回两边都能匹配上的
FROM servers s JOIN owners o ON s.owner_id = o.owner_id
-- LEFT JOIN:左表全要,右表匹配不上的填 NULL
FROM servers s LEFT JOIN owners o ON s.owner_id = o.owner_id
-- RIGHT JOIN:右表全要,左表匹配不上的填 NULL(用得少)LEFT JOIN 最常用——以左表为主,右表有的就关联,没有的字段是 NULL。比如查所有服务器及其负责人,有的服务器没分配负责人也要列出来,用 LEFT JOIN。
sql
SELECT s.hostname, o.name
FROM servers s
LEFT JOIN owners o ON s.owner_id = o.owner_id;text
hostname | name
web-01 | 张三
web-02 | 李四
db-01 | NULL -- 这台没分配负责人,LEFT JOIN 保留这行二、GROUP BY:分组统计
统计每个环境有多少台服务器:
sql
SELECT env, COUNT(*) AS cnt
FROM servers
GROUP BY env;text
env | cnt
prod | 15
test | 8
dev | 3GROUP BY env 按环境分组,COUNT(*) 算每组多少行。
常用聚合函数:
| 函数 | 作用 |
|---|---|
COUNT(*) | 行数 |
SUM(字段) | 求和 |
AVG(字段) | 平均 |
MAX(字段) | 最大 |
MIN(字段) | 最小 |
每个环境的服务器数量 + 状态分布:
sql
SELECT env, status, COUNT(*) AS cnt
FROM servers
GROUP BY env, status
ORDER BY env, cnt DESC;按两个字段分组(env + status),看每个环境各状态有多少台。
HAVING:过滤分组结果
WHERE 在分组前过滤行,HAVING 在分组后过滤组。找服务器数超过 5 的环境:
sql
SELECT env, COUNT(*) AS cnt
FROM servers
GROUP BY env
HAVING cnt > 5;WHERE 过滤原始行(比如 WHERE status = 1),HAVING 过滤聚合后的结果(比如 HAVING COUNT(*) > 5)。两者区别:WHERE 不能用聚合函数,HAVING 可以。
三、常用函数
字符串
sql
-- 拼接
SELECT CONCAT(hostname, ':', ip_addr) AS node FROM servers;
-- NULL 处理:remark 为 NULL 时显示空串
SELECT hostname, IFNULL(remark, '') AS remark FROM servers;IFNULL(字段, 默认值) 是高频函数——查出来的 NULL 在应用里处理麻烦,这里直接转成空串或指定值。
时间
sql
SELECT NOW(); -- 当前时间
SELECT CURRENT_DATE(); -- 当前日期
SELECT DATE_FORMAT(created_at, '%Y-%m') AS month FROM servers; -- 格式化按月统计创建量:
sql
SELECT DATE_FORMAT(created_at, '%Y-%m') AS month, COUNT(*) AS cnt
FROM servers
GROUP BY month
ORDER BY month;条件转换
sql
SELECT hostname,
CASE status
WHEN 1 THEN 'online'
ELSE 'offline'
END AS status_name
FROM servers;CASE WHEN 把状态码转成可读的字符串。
JSON 函数
8.0+ 支持 JSON 类型。如果 extra 字段是 JSON:
sql
-- 取 JSON 里的某个 key
SELECT hostname, JSON_EXTRACT(extra, '$.role') AS role
FROM servers;
-- 简写(8.0+)
SELECT hostname, extra->'$.role' AS role FROM servers;-> 是 JSON_EXTRACT 的简写。但前面说过,频繁查询的 JSON key 应该拆成独立列——JSON 函数查询慢且不好建索引。
四、子查询
查询里嵌查询。找负责人是"张三"的服务器:
sql
SELECT hostname FROM servers
WHERE owner_id = (
SELECT owner_id FROM owners WHERE name = '张三'
);子查询先算出张三的 owner_id,外层用这个值查。简单场景这么写清晰,但关联子查询(外层每行都跑一次子查询)可能慢:
sql
-- 关联子查询(每行都跑一次,慢)
SELECT hostname FROM servers s
WHERE EXISTS (
SELECT 1 FROM owners o WHERE o.owner_id = s.owner_id AND o.name LIKE '张%'
);能用 JOIN 替代的子查询,优先 JOIN——通常更快:
sql
-- 等价的 JOIN 写法,一般更快
SELECT s.hostname
FROM servers s
JOIN owners o ON s.owner_id = o.owner_id
WHERE o.name LIKE '张%';五、查询写法原则
- 能 JOIN 别用子查询——JOIN 通常更优,优化器更好处理
- 聚合先过滤——
WHERE在GROUP BY前减少参与聚合的行数 - 分页用 LIMIT 加 OFFSET——大表深翻页(OFFSET 几百万)慢,用"记住上次最大 id"的方式替代
- 少用 SELECT *——取需要的字段
- 避免 SELECT 里做计算——
WHERE 字段+1 = 2会让索引失效,写成WHERE 字段 = 1
查询进阶之后
能关联多表、能分组统计、能用函数了。但这些查询快不快,取决于有没有索引、索引建得对不对。同样一条查询,有索引毫秒返回,没索引扫全表几秒甚至几十秒。下一篇讲索引原理——B+Tree、最左前缀、为什么索引会失效。