-- 按 CASE 表达式自定义排序优先级 SELECT id, status, total FROM orders ORDERBY CASE status WHEN'pending'THEN1 WHEN'paid'THEN2 WHEN'shipped'THEN3 WHEN'done'THEN4 WHEN'cancelled'THEN5 END;
ORDER BY 与 GROUP BY 的配合
聚合查询中,ORDER BY 可以使用聚合结果排序:SELECT city, COUNT(*) AS cnt FROM users GROUP BY city ORDER BY cnt DESC; 这里的 cnt 是别名,PostgreSQL 允许在 ORDER BY 中引用 SELECT 中的别名.
-- 游标分页:基于上一页最后一条的 id 继续查 -- 第 1 页 SELECT*FROM orders ORDERBY id LIMIT 10;
-- 第 2 页(假设上一页最后一条 id = 10) SELECT*FROM orders WHERE id >10ORDERBY id LIMIT 10;
-- 优势:无论第几页,性能恒定(利用 id 索引直接定位)
子查询概述
子查询就是"查询里面套查询".根据返回结果的形状分三类:
类型
返回
典型用法
标量子查询
单个值(1行1列)
放在 SELECT / WHERE / HAVING 中当作一个值用
行子查询
一行多列
与行比较 WHERE (a, b) = (SELECT ...)
表子查询
多行多列
放在 FROM 中当作临时表,IN / EXISTS 判断
标量子查询
-- 在 SELECT 中:每个商品价格与平均价格的差值 SELECT name, price, price - (SELECTAVG(price) FROM products) AS diff_from_avg FROM products ORDERBY diff_from_avg DESC;
-- 在 WHERE 中:查询价格高于平均价的商品 SELECT name, price FROM products WHERE price > (SELECTAVG(price) FROM products);
-- 在 HAVING 中:哪些分类的平均价格高于全局平均 SELECT c.name, ROUND(AVG(p.price), 2) AS avg_price FROM categories c JOIN products p ON p.category_id = c.id GROUPBY c.name HAVINGAVG(p.price) > (SELECTAVG(price) FROM products);
-- 先聚合,再筛选(等价于 HAVING,但有时更清晰) SELECT*FROM ( SELECT user_id, COUNT(*) AS cnt, SUM(total) AS total_spent FROM orders WHERE status ='done' GROUPBY user_id ) AS user_stats WHERE total_spent >5000 ORDERBY total_spent DESC;
IN 子查询
-- 查询购买过"电子产品"的用户 SELECT username FROM users WHERE id IN ( SELECTDISTINCT o.user_id FROM orders o JOIN order_items oi ON oi.order_id = o.id JOIN products p ON p.product_id = oi.product_id WHERE p.category_id =1 );
-- NOT IN:查询从未下过单的用户 SELECT username FROM users WHERE id NOTIN (SELECTDISTINCT user_id FROM orders);
NOT IN 与 NULL 的陷阱
如果子查询结果中包含 NULL,NOT IN 会返回空结果. 因为 x NOT IN (1, 2, NULL) 等价于 x != 1 AND x != 2 AND x != NULL,最后一个条件永远是 UNKNOWN. 安全做法:用 NOT EXISTS 替代.
EXISTS 与 IN
EXISTS 检查"子查询是否有结果",不关心具体返回什么值:
-- 查询至少下过一单的用户(EXISTS 写法) SELECT u.username FROM users u WHEREEXISTS ( SELECT1FROM orders o WHERE o.user_id = u.id );
-- 查询从未写过评价的用户(NOT EXISTS) SELECT u.username FROM users u WHERENOTEXISTS ( SELECT1FROM reviews r WHERE r.user_id = u.id ); -- frank, henry(这两个用户没有评价记录)
EXISTS vs IN:如何选择?
场景
推荐
原因
子查询结果集小
IN
简洁,优化器能内联
子查询结果集大
EXISTS
找到第一条匹配就停止扫描
子查询可能含 NULL
EXISTS
避免 NOT IN + NULL 的陷阱
需要关联外层条件
EXISTS
天然支持相关子查询
-- 相关子查询:查询"订单金额超过该用户平均订单金额"的订单 SELECT o.id, o.user_id, o.total FROM orders o WHERE o.total > ( SELECTAVG(o2.total) FROM orders o2 WHERE o2.user_id = o.user_id ) ORDERBY o.user_id, o.total DESC;
CTE - 公用表表达式
CTE(Common Table Expression)用 WITH 关键字定义,可以理解为"给子查询起个名字".好处:
可读性:复杂查询拆成多个命名步骤
可复用:同一个 CTE 可在主查询中多次引用
递归:支持递归查询(树形结构遍历等)
-- 基础 CTE:先算每用户的订单统计,再筛选 WITH user_orders AS ( SELECT user_id, COUNT(*) AS order_count, SUM(total) AS total_spent FROM orders WHERE status !='cancelled' GROUPBY user_id ) SELECT u.username, u.city, uo.order_count, uo.total_spent FROM users u JOIN user_orders uo ON uo.user_id = u.id WHERE uo.total_spent >5000 ORDERBY uo.total_spent DESC;
-- 多个 CTE 组合:用户消费排名 + 评价活跃度 WITH spending AS ( SELECT user_id, SUM(total) AS total_spent FROM orders WHERE status ='done' GROUPBY user_id ), reviewing AS ( SELECT user_id, COUNT(*) AS review_count, ROUND(AVG(rating), 2) AS avg_given FROM reviews GROUPBY user_id ) SELECT u.username, COALESCE(s.total_spent, 0) AS total_spent, COALESCE(r.review_count, 0) AS review_count, r.avg_given FROM users u LEFTJOIN spending s ON s.user_id = u.id LEFTJOIN reviewing r ON r.user_id = u.id ORDERBY total_spent DESC;
-- CTE 中引用前面的 CTE(链式) WITH monthly_sales AS ( SELECT TO_CHAR(created_at, 'YYYY-MM') ASmonth, SUM(total) AS revenue FROM orders WHERE status ='done' GROUPBY TO_CHAR(created_at, 'YYYY-MM') ), avg_monthly AS ( SELECT ROUND(AVG(revenue), 2) AS avg_revenue FROM monthly_sales ) SELECT ms.month, ms.revenue, am.avg_revenue, CASEWHEN ms.revenue > am.avg_revenue THEN'above'ELSE'below'ENDAS vs_avg FROM monthly_sales ms, avg_monthly am ORDERBY ms.month;
-- 3. 余额高于平均 SELECT username, balance FROM users WHERE balance > (SELECTAVG(balance) FROM users) ORDERBY balance DESC; -- eve(15000), alice(5000), grace(2200)
-- 4. 从未被购买的商品 SELECT p.name FROM products p WHERENOTEXISTS ( SELECT1FROM order_items oi WHERE oi.product_id = p.id ); -- 羽绒服, 算法导论
-- 5. CTE + 销量 Top 3 WITH product_sales AS ( SELECT product_id, SUM(quantity) AS total_sold FROM order_items GROUPBY product_id ) SELECT p.name, ps.total_sold FROM product_sales ps JOIN products p ON p.id = ps.product_id ORDERBY ps.total_sold DESC LIMIT 3; -- AirPods Pro: 4, MacBook Pro 14: 3, iPhone 15: 3
-- 6. 至少购买过 2 种不同分类 SELECT u.username FROM users u JOIN orders o ON o.user_id = u.id JOIN order_items oi ON oi.order_id = o.id JOIN products p ON p.id = oi.product_id GROUPBY u.id, u.username HAVINGCOUNT(DISTINCT p.category_id) >=2; -- eve, dave, bob, grace
-- 7. 每用户最大单笔订单(相关子查询) SELECT o.id, o.user_id, u.username, o.total FROM orders o JOIN users u ON u.id = o.user_id WHERE o.total = ( SELECTMAX(o2.total) FROM orders o2 WHERE o2.user_id = o.user_id ) ORDERBY o.total DESC;
-- 8. 月度报表 + 增长率(自连接方式,窗口函数版本见下一篇) WITH monthly AS ( SELECT TO_CHAR(created_at, 'YYYY-MM') ASmonth, COUNT(*) AS order_count, SUM(total) AS revenue FROM orders GROUPBY TO_CHAR(created_at, 'YYYY-MM') ) SELECT m1.month, m1.order_count, m1.revenue, ROUND((m1.revenue - m2.revenue) / m2.revenue *100, 1) AS growth_pct FROM monthly m1 LEFTJOIN monthly m2 ON m2.month = TO_CHAR( (TO_DATE(m1.month, 'YYYY-MM') -INTERVAL'1 month'), 'YYYY-MM' ) ORDERBY m1.month;
-- 9. 买了 MacBook 但没写评价 SELECT u.username FROM users u WHEREEXISTS ( SELECT1FROM orders o JOIN order_items oi ON oi.order_id = o.id WHERE o.user_id = u.id AND oi.product_id =1 ) ANDNOTEXISTS ( SELECT1FROM reviews r WHERE r.user_id = u.id AND r.product_id =1 ); -- carol
第 2 题 OFFSET 1 跳过第 1 条,LIMIT 3 取接下来 3 条. 第 4 题 NOT EXISTS 比 NOT IN 更安全(不受 NULL 影响). 第 7 题对每行 order 做相关子查询找到同 user_id 的最大 total 进行比对. 第 8 题用自连接把"当月"和"上月"拼在一起算增长率. 第 9 题用两个 EXISTS 组合"满足条件 A 但不满足条件 B"的逻辑.