CTE命名需体现数据逻辑,如active_users_30d;每个CTE只做一件事,避免混杂清洗、关联、聚合;慎用递归CTE,优先用JOIN或窗口函数;格式上空行分隔、AS后换行、字段分行书写。

CTE命名必须体现数据逻辑,不能只叫temp1或t
很多人写WITH temp1 AS (...), temp2 AS (...),结果自己三天后都看不懂哪段在算什么。命名不是为了“能跑”,而是让别人(包括未来的你)一眼抓住意图。
实操建议:
- 用名词短语描述结果集本质,比如
active_users_30d比users_filter清楚,order_revenue_by_region比agg1可维护 - 避免动词开头(如
get_active_users),CTE是数据集,不是函数 - 如果涉及时间范围,显式写进名字:
daily_sales_jan2024,别依赖注释 - 多层嵌套时,下层CTE名可体现依赖关系,例如
user_orders→orders_with_ltv→high_value_cohorts
每个CTE只做一件事,禁止在SELECT里塞一堆CASE WHEN和子查询
把清洗、关联、聚合全堆在一个CTE里,等于把三层逻辑压成一层——表面代码变少,实际阅读成本翻倍。SQL不是越短越好,是越分得清越好。
常见错误现象:
-
SELECT user_id, CASE WHEN ... END AS status, (SELECT COUNT(*) FROM ...) AS order_cnt, ...—— 这个CTE同时干了状态判断、子查询聚合、字段拼接 - 后续CTE还得基于这个“大杂烩”再过滤,导致执行计划难优化,也容易漏掉NULL处理
正确做法是拆开:
- 先用
raw_users取基础用户表 - 再用
enriched_users加状态字段(单独CASE) - 用
user_order_counts单独聚合订单数(GROUP BY) - 最后
final_report做JOIN和筛选
慎用递归CTE,非树形/层级场景别硬套WITH RECURSIVE
看到“自关联”就条件反射写递归CTE,是很多中级SQL手的惯性。但递归不是银弹——它难调试、易爆栈、MySQL 8.0前还不支持,PostgreSQL里默认max_recursion_depth=100,超了直接报错ERROR: stack depth limit exceeded。
使用场景要严格匹配:
- 真正有父子层级:组织架构、评论回复链、BOM物料清单
- 需要逐层展开的路径计算:比如从A到B的最短跳转路径(但这类通常更适合图数据库)
- 替代方案更稳:用窗口函数
LAG()/LEAD()处理相邻行,用多次LEFT JOIN展开固定层级(如三级部门)
如果只是想“查上级部门名称”,一条JOIN dept d2 ON d1.parent_id = d2.id比递归清晰十倍。
CTE之间用空行分隔,结尾不加逗号,AS后换行写SELECT
格式不是审美问题,是协作底线。没有空行的CTE块像一坨意大利面,尤其当某段SELECT带10个字段+3个JOIN时,眼睛根本找不到下一个WITH在哪。
实操细节:
- 每个CTE定义后空一行,包括最后一个CTE和主
SELECT之间 -
AS后面立刻换行,SELECT顶格写,别缩进或挤在AS同一行 - 所有逗号放字段行尾,而非行首;CTE列表末尾不加逗号(避免Git diff误增行)
- 字段多于3个时,每行一个字段,方便增删和Code Review时定位
容易被忽略的是:CTE本身不建索引,也不物化(除非数据库明确支持MATERIALIZED),所以别指望“写一遍,后面随便用”能提升性能——该重复写的子查询,还是得重复写,或者改用临时表。

















