问题与目标
业务问题很少只是“查询整张表”,更常见的是按条件过滤、按项目统计、关联名称,并保留明细排名。本篇在上一节数据模型上组织这些查询。
完成标准:能使用 WHERE、聚合、GROUP BY、HAVING、JOIN、子查询、CTE 和窗口函数,并解释聚合结果与明细结果的区别。
核心概念
理解查询可按逻辑顺序思考:FROM/JOIN 形成数据源,WHERE 过滤行,GROUP BY 分组,HAVING 过滤组,SELECT 生成列,ORDER BY 排序,LIMIT 截取。
WHERE 发生在聚合前,不能直接过滤聚合结果;HAVING 发生在分组后。INNER JOIN 只保留匹配行,LEFT JOIN 保留左表全部行。窗口函数计算同组排名或累计值,但不把明细折叠成一行。
基础表达式同样影响结果:DISTINCT 去重,LIKE 做通配匹配,IS NULL 判断空值,CASE 把条件映射为展示值。SQL 中与 NULL 的普通比较结果不是 true,因此不能写 = NULL。
可运行实现
先补充一个项目和任务:
USE engineering_notes;
INSERT INTO projects (name) VALUES ('api-service');
INSERT INTO tasks (project_id, title, status) VALUES
(1, 'write SQL queries', 'done'),
(2, 'define routes', 'doing'),
(2, 'add tests', 'todo'),
(2, 'write logs', 'done');
过滤与排序:
SELECT id, title, status
FROM tasks
WHERE status IN ('todo', 'doing')
AND title LIKE '%API%'
ORDER BY created_at DESC, id DESC;
用 CASE 生成展示标签,并处理可空截止时间:
SELECT DISTINCT status,
CASE status
WHEN 'todo' THEN '待处理'
WHEN 'doing' THEN '进行中'
ELSE '已完成'
END AS status_label
FROM tasks
WHERE due_at IS NULL;
按项目统计,并只保留至少两个任务的项目:
SELECT p.id, p.name, COUNT(t.id) AS task_count
FROM projects AS p
LEFT JOIN tasks AS t ON t.project_id = p.id
GROUP BY p.id, p.name
HAVING COUNT(t.id) >= 2
ORDER BY task_count DESC;
CTE 先统计完成数,再计算比例:
WITH task_stats AS (
SELECT project_id,
COUNT(*) AS total_count,
SUM(status = 'done') AS done_count
FROM tasks
GROUP BY project_id
)
SELECT p.name, s.done_count, s.total_count,
ROUND(s.done_count / s.total_count * 100, 1) AS done_percent
FROM task_stats AS s
JOIN projects AS p ON p.id = s.project_id;
为每个项目内的任务编号:
SELECT project_id, title, created_at,
ROW_NUMBER() OVER (
PARTITION BY project_id
ORDER BY created_at, id
) AS project_task_number
FROM tasks;
这些查询的输入是 projects/tasks 两表记录,输出分别是筛选后的任务、每个项目的统计结果和保留明细的组内编号。验收重点是手工用少量数据核对行数与排序,再逐步扩大数据量。
排除没有完成任务的项目时,EXISTS 往往比拼接 ID 列表更直接:
SELECT p.id, p.name
FROM projects AS p
WHERE EXISTS (
SELECT 1
FROM tasks AS t
WHERE t.project_id = p.id AND t.status = 'done'
);
合并结构一致的两个结果集使用 UNION ALL;只有确实需要去重时才使用 UNION。分页可以写 LIMIT 20 OFFSET 40,但数据量大、页码深时应改用稳定排序字段做游标分页,例如 WHERE id > ? ORDER BY id LIMIT 20。
常见问题与排查
LEFT JOIN后在WHERE中过滤右表:可能把没有匹配项的左表行删掉;条件是否放在ON中取决于目标。- 分组查询选择了未分组列:结果含义不确定,开启严格 SQL 模式能更早暴露问题。
COUNT(*)与COUNT(column)混淆:后者不统计NULL;左连接统计子表常用COUNT(t.id)。NOT IN子查询含NULL:结果可能出乎预期,排除关系优先考虑NOT EXISTS。- 没有
ORDER BY却依赖返回顺序:数据库不保证自然顺序。 LIKE '%word%'在大表上变慢:前导通配符通常难以利用普通 B-tree 索引,需要重新设计查询、搜索字段或引入全文搜索。- 分页出现重复或遗漏:排序字段不唯一;在时间字段后追加主键形成稳定顺序。
小结
复杂查询应按数据源、行过滤、分组、组过滤、展示和排序逐层构造。窗口函数补足了“保留明细同时做组内计算”的能力。
许可协议:CC BY-NC 4.0
更新于 2 小时前
觉得文章有帮助?点个赞吧!
0 条评论


