免费 SQL 格式化工具
使用可自定义的缩进和关键词大小写格式化、美化 SQL 查询。
SQL 格式化到底在做什么
SQL 在解析时对空白不敏感,每一种数据库引擎读取 SELECT name,age FROM users WHERE active=1 和读取多行缩进版本是完全等价的。看得见的差别纯粹是给人类读者看的。一个格式化工具通过一个分词器读取 SQL,识别出关键字、标识符、字面量、运算符和标点,然后用约定俗成的缩进、子句之间的换行、统一的关键字大小写以及运算符前后一致的间距,把同一条查询重新输出一遍。数据语义并没有改变(执行格式化后的查询会得到完全相同的结果)但在一条 200 行的分析查询上,可读性的差别就是「我能调试这个」与「我干脆从头重写一遍」之间的差别。
SQL,被格式化的这门语言的简史
SQL(Structured Query Language)由 IBM 的 Donald D. Chamberlin 和 Raymond F. Boyce 在 1970 年代初期开发,最初叫 SEQUEL(「Structured English Query Language」),因为商标冲突而被改名为 SQL。这门语言最早由 Oracle 的前身(Relational Software, Inc.)于 1979 年商业化,Oracle V2 是第一个商用 SQL 数据库,比 IBM 自家的 DB2 还早进入市场。第一个 ANSI 标准 ANSI SQL-86 于 1986 年发布;范围大得多的 SQL-92 修订(那个「大」SQL 标准,ISO/IEC 9075:1992)定义了今天的使用者所熟悉的大部分语法,JOIN 语法、集合运算、子查询、事务。后续修订加入了对象-关系特性(SQL:1999)、XML 支持(SQL:2003)、窗口函数与 CTE(SQL:2003 与 2008)、时态查询(SQL:2011)、JSON 支持(SQL:2016,并在 SQL:2023 中扩展)。每个主要数据库厂商都实现了自己的方言,MySQL、PostgreSQL、SQLite、Microsoft SQL Server(T-SQL)、Oracle(PL/SQL)、DuckDB(与 PostgreSQL 兼容的分析型 SQL)、BigQuery、Snowflake、Redshift、ClickHouse、Databricks SQL,它们共享同一个 SQL-92 核心,但在函数、语法扩展和保留字列表上各自有所发散。一个好的格式化工具会尊重它正在格式化的那个方言,因为同一个标识符在一种方言里是关键字,在另一种方言里却可能是合法的列名。
让 SQL 易读的那些约定
在 SQL 风格指南里,有几条约定已经成了标准,Mozilla 的、被广泛引用的 Simon Holywell 的 SQL Style Guide、GitLab 数据团队的约定、dbt 推荐的风格。关键字大写(SELECT、FROM、WHERE、JOIN)、标识符(表名和列名)小写或采用 snake_case 是占主导地位的约定。一些风格指南主张相反(小写关键字更容易输入也更易读),而 PostgreSQL 自己的方言文档全文都用小写关键字(但在大多数专业代码库里大写关键字胜出,因为它们能让语言结构一眼可见。每个主要子句单独占一行:SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY、LIMIT)每一条都在同一缩进层级开启新行。JOIN 条件缩进在 ON 之下:JOIN 关键字单独一行,ON 关键字在下一行再缩进一级。逗号在前还是逗号在后是列清单里一场旷日持久的风格之争;逗号在前(每一行列开头都是逗号)在添加/删除列时能产生更干净的 diff,并能避免末尾多余逗号导致的语法错误,但视觉上让很多人不太适应。逗号在后在生产代码里更常见。子查询缩进:嵌套查询比父查询多缩进一级。CTE(WITH)子句:每个具名 CTE 单独一行,主体缩进在 AS 之下,主查询在最底部左对齐。
什么时候你会去用 SQL 格式化工具
- 读 ORM 打出来的查询。Hibernate、Sequelize、ActiveRecord、Prisma、SQLAlchemy、Django ORM,这些都会以编程方式生成 SQL,并把它作为单独一行密密麻麻的字符串发到你的应用日志里。要排查「这条查询为什么这么慢」,把日志里的 SQL 粘进格式化工具,看看 ORM 实际生成的是什么。结果常常很有启发(「啊,这居然是 CROSS JOIN,我以为是 INNER」)。
- 数据库 PR 的 code review。一次 SQL 迁移或存储过程的改动,在格式一致的前提下要好审得多。提交前把文件过一遍格式化工具,让 diff 反映出真正的结构变化,而不是空白噪声。
- 把 EXPLAIN 计划和查询并排看。调优一条慢查询时,你会先读 EXPLAIN 输出,然后再回到查询里去找计划顶端那张表对应的引用。一份格式化好的查询能让这种交叉对照瞬间完成。
- 把 SQL 粘到工单、文档、Slack 里。格式化好的查询读起来清清爽爽;压缩成一行的查询在任何宽度有限的地方都几乎没法读。分享之前先格式化。
- 重新格式化关键字大小写混乱的遗留查询。一段已经存在 15 年的代码库,里面 SELECT 有时写作
Select,有时是select,有时是SELECT,让格式化工具把所有写法统一到一种约定之后,再读就顺多了。 - 清理从 BI 工具里导出来的查询。Looker、Mode、Metabase、Tableau 等等工具都允许你把底层 SQL 复制出来,但它们会用各自临时拼出来的格式输出。读之前先格式化。
SQL 格式化工具的生态
对于命令行和编辑器集成的工作流,已经有不少成熟的选项。sql-formatter(npm 包,最初由 Andriy Isayev 开发,现在由社区团队维护)是 JavaScript 生态中最主流的 SQL 格式化工具,支持 MySQL、PostgreSQL、SQLite、Standard SQL、BigQuery、Redshift、Snowflake、Spark、TiDB、MariaDB 以及若干其他方言。pg_format(Gilles Darold)是 PostgreSQL 专用的代表性格式化工具,用 Perl 写成,并随知名的 pgFormatter 网络服务一起提供。Poor Man's T-SQL Formatter(Tao Klerks,2011)专门针对 Microsoft SQL Server 的 T-SQL 方言。sqlparse(Andi Albrecht)是标准的 Python 库,被 Django 用于查询分析,也被无数数据工程脚本使用。SQLFluff 是现代的 linter 兼格式化工具,与 dbt 项目和分析工作流集成在一起。JetBrains 的 DataGrip 以及 IntelliJ IDEA 的 SQL 插件都自带一个相当成熟、感知方言的格式化工具;VS Code 的 SQLTools 和其他几个扩展则是在 npm 的 sql-formatter 上做了一层封装。对于带有构建管线的项目,今天的常见模式是「在编辑器中保存时格式化 + 一道 CI 检查,如果 SQL 文件没有按规则格式化就让构建失败」,和 JavaScript 用 Prettier、Python 用 Black 是同一个思路。
对格式化有影响的方言差异
大多数 SQL 格式化工具在做点儿微调之后能跨方言工作,但少数几类差异要求格式化工具知道自己读的是哪个方言。带引号的标识符:标准 SQL 用双引号("order");MySQL 默认用反引号(`order`),只有在 ANSI 模式下才尊重双引号;SQL Server 用方括号([order])。字符串拼接:标准 SQL 用 ||;MySQL 用 CONCAT(),或是在 ANSI 模式下偶尔用 ||;SQL Server 用 +。分页:MySQL/PostgreSQL/SQLite 用 LIMIT/OFFSET;SQL Server 用 TOP 或 OFFSET FETCH;Oracle 用 FETCH 或 ROWNUM。自增:MySQL 里是 AUTO_INCREMENT,PostgreSQL 里是 SERIAL 或 IDENTITY,SQL Server 里是 IDENTITY,SQLite 里是 AUTOINCREMENT。保留字列表各方言之间也有差异,一个名为 rank 的列在 PostgreSQL 里需要加引号(因为 RANK 是窗口函数关键字),但在 MySQL 里作为标识符就完全没问题。一个不知道方言的格式化工具可能会通过加上不合适的引号来弄坏本来合法的查询。一个通用格式化工具的「Format SQL」输出在标准的 SELECT/INSERT/UPDATE/DELETE 形态下通常都是正确的;对于厂商特有的语法(窗口函数、提示、系统函数),请把输出和你所在方言的文档抽样比对一下。
隐私:为什么对 SQL 来说「只在浏览器里跑」尤其重要
在任何一家组织里,SQL 查询都属于最敏感的那部分文本:它们会暴露内部表名(这反过来透露了产品分类)、列名(透露了数据模型)、过滤值(其中可能包含真实的用户 ID、邮箱地址、业务 ID)、在更老的代码模式里直接嵌入的明文凭证,以及公司可能视为机密的那部分分析结构。服务端的 SQL 格式化工具会把每一条查询都顺手存进它们自己的日志里。这个格式化工具完全在你的浏览器里通过 JavaScript 运行,你点 Format 时打开 DevTools 的 Network 选项卡看一下(不会有任何请求被发出),或者在页面加载之后让它离线(飞行模式),它仍然能继续工作。对于含有真实表名和 PII 过滤值的生产查询、内部分析 SQL、ETL 管线,或任何你不希望被复制到陌生人硬盘上的查询来说,都是安全的。
常见问题
格式化工具能自动把 SQL 关键字大写吗?
可以,Keywords 这个开关用来控制大小写。UPPERCASE(全大写)是占主导地位的专业约定,因为它能让语言结构一眼可见(SELECT、FROM、WHERE 会从标识符里凸显出来);也有一些风格指南偏好小写(PostgreSQL 自己的文档全文使用小写),理由是更易读、敲起来也更快。两种做法都行,关键是你们团队选定一种并保持一致。大小写本身对查询行为没有任何影响,SQL 关键字在解析时是大小写不敏感的。
我怎么自定义缩进?
Indent 下拉框支持 2 个空格(当今 Web 上占主导的现代约定)、4 个空格(在比较老的 Java 和 .NET 团队里仍然常见),或者 Tab 字符。在新一点的代码库里,2 个空格通常会胜出;对 SQL 尤其如此,因为深嵌套的子查询在 100 字符行宽限制内能更舒服地展开。跟着你们团队既有的约定走就好;一致性比具体的选择本身更重要。
它对大查询能用吗?
可以,因为格式化是在你的浏览器里跑的,实际的天花板就是你设备的可用内存。几百行的 SQL 在远不到一秒钟里就能格式化完成;上千行的查询(在数据仓库 ETL 中很常见)也能跑,只是在解析器走完整个结构期间,标签页可能会短暂卡一下。如果是要批量重新格式化整个 SQL 仓库,用命令行工具更合适(npm 上的 sql-formatter、面向 PostgreSQL 的 pg_format、面向 dbt 项目的 SQLFluff)。
格式化工具能识别我用的特定 SQL 方言吗?
在 MySQL、PostgreSQL、SQLite、T-SQL 和标准 ANSI SQL 里,对常见的 SELECT/INSERT/UPDATE/DELETE/JOIN/CTE/子查询形态它都能正确处理。厂商特定的语法(窗口函数子句、MERGE 语句、方言特有的提示、系统函数、PostgreSQL 风格的数组运算符、T-SQL 的 XML 方法)可能格式化得并不完美。请抽查输出,并在提交之前把格式化后的查询放到你的数据库里跑一下,确认它能被正确解析。
我的 SQL 会被上传吗?
不会。格式化完全在你的浏览器里完成,你粘贴的查询永远不会离开你的设备。你点 Format 时打开 DevTools 的 Network 选项卡看一下(不会有任何请求被发出),或者在页面加载之后让它离线(飞行模式)。对于含有敏感表名、PII 过滤值,或受 NDA、合规规定约束的任何 SQL,都是安全的。