DevToolBox免费
博客

SQL 格式化最佳实践:可读查询的风格指南

7 分钟阅读作者 DevToolBox

混乱的 SQL 难以调试、审查痛苦、维护更是噩梦。无论你是独立开发还是团队协作,一致的 SQL 格式化都能将难以阅读的文本墙转变为清晰、有逻辑的语句。本指南涵盖 SQL 风格的方方面面——从关键字大小写和缩进到 CTE 格式化和命名约定——附有即学即用的前后对比示例。

1. 为什么 SQL 格式化很重要

SQL 常被当作"写一次"的语言对待,但生产环境的查询会被阅读几十次。良好的格式化在三个关键领域带来回报:

  • 可读性——格式良好的 SQL 让你一眼就能扫描查询结构。你可以立即看到连接了哪些表、应用了什么过滤条件以及选择了哪些列。
  • 代码审查——审查者可以专注于逻辑而不是解读结构。当每个子句占据独立的行时,diff 视图才有意义。
  • 调试——当查询返回错误结果时,格式化的 SQL 让你可以隔离每个子句并独立测试。找到放错位置的 AND 或错误的 JOIN 条件只需几秒而不是几分钟。
  • 可维护性——六个月后,你(或你的队友)需要修改那个查询。格式化的 SQL 使修改安全且可预测。

2. 关键字:大写 vs 小写

SQL 关键字最常见的两种约定:

风格 A:大写关键字(最常见)

大写关键字在视觉上将 SQL 结构与数据标识符分开。这是最广泛采用的约定,推荐团队使用。

SELECT
    user_id,
    first_name,
    last_name,
    email
FROM users
WHERE is_active = TRUE
ORDER BY created_at DESC
LIMIT 100;

风格 B:小写关键字

一些开发者偏好小写以获得更现代的感觉。这在现代编辑器的语法高亮下效果很好。

select
    user_id,
    first_name,
    last_name,
    email
from users
where is_active = true
order by created_at desc
limit 100;

结论:选择一种并在团队中强制执行。大写是行业默认标准,在纯文本环境(日志、Slack、邮件)中没有语法高亮时效果最佳。

3. 缩进:Tab vs 空格,一行一个子句

黄金法则:每行一个主要子句。这使 diff 保持干净,子句易于注释。

  • 使用一致的缩进(2 或 4 个空格;避免使用 Tab 以确保可移植性)。
  • 每个主要关键字(SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY、LIMIT)在同一缩进级别的新行开始。
  • 子子句(JOIN 条件、AND/OR 过滤器)缩进一级。
  • 当能提高可读性时,垂直对齐相关元素。
-- Each major clause on its own line, sub-clauses indented
SELECT
    u.user_id,
    u.email,
    COUNT(o.order_id) AS order_count
FROM users AS u
LEFT JOIN orders AS o
    ON u.user_id = o.user_id
WHERE u.is_active = TRUE
    AND u.created_at >= '2024-01-01'
GROUP BY
    u.user_id,
    u.email
HAVING COUNT(o.order_id) > 5
ORDER BY order_count DESC
LIMIT 20;

4. SELECT:一行一列,尾部逗号 vs 前导逗号

每行列出一列使添加、删除或重新排序列变得轻而易举。逗号位置的争论分为两个阵营:

尾部逗号(更常见)

SELECT
    user_id,
    first_name,
    last_name,
    email,
    created_at
FROM users;

前导逗号

SELECT
    user_id
    , first_name
    , last_name
    , email
    , created_at
FROM users;

前导逗号更容易发现缺失的逗号,也更容易注释掉最后一列。但尾部逗号更自然,也是更常见的选择。

提示:无论选择哪种,在生产查询中始终将每列放在独立的行上。不要在一行中挤入多列。

5. JOIN:格式化多表查询

JOIN 是格式化回报最大的地方。格式良好的 JOIN 块清楚地展示了表之间的关系。

  • 每个 JOIN 在与 FROM 相同级别的新行开始。
  • ON 子句缩进在其 JOIN 下方。
  • 多条件 ON 子句在单独缩进的行上使用 AND。
  • 始终使用显式 JOIN 语法(永远不要在 FROM 中使用逗号分隔的表)。
SELECT
    o.order_id,
    o.order_date,
    c.customer_name,
    p.product_name,
    oi.quantity,
    oi.unit_price
FROM orders AS o
INNER JOIN customers AS c
    ON o.customer_id = c.customer_id
INNER JOIN order_items AS oi
    ON o.order_id = oi.order_id
INNER JOIN products AS p
    ON oi.product_id = p.product_id
    AND p.is_active = TRUE
WHERE o.order_date >= '2024-01-01'
ORDER BY o.order_date DESC;

6. WHERE:AND/OR 对齐,子查询缩进

WHERE 子句可能很快变得复杂。适当的格式化保持逻辑树可见。

  • 每个 AND/OR 在新行开始,与第一个条件对齐或缩进在 WHERE 下方。
  • 对混合的 AND/OR 逻辑显式使用括号。
  • 将子查询缩进为嵌套块。
  • 将运算符(AND/OR)放在行首,而不是行尾。
-- Simple WHERE with AND
WHERE u.is_active = TRUE
    AND u.email_verified = TRUE
    AND u.created_at >= '2024-01-01'

-- Mixed AND/OR with explicit parentheses
WHERE u.is_active = TRUE
    AND (
        u.role = 'admin'
        OR u.role = 'moderator'
    )
    AND u.last_login >= '2024-06-01'

-- Subquery in WHERE
WHERE u.user_id IN (
    SELECT o.user_id
    FROM orders AS o
    WHERE o.total_amount > 1000
        AND o.order_date >= '2024-01-01'
    GROUP BY o.user_id
    HAVING COUNT(*) >= 3
)

7. CTE(WITH):格式化公共表表达式

CTE 是提高查询可读性的最佳工具之一。良好的格式化会放大其好处。

  • WITH 在顶层开始。
  • 每个 CTE 名称和 AS 在同一行。
  • CTE 主体在括号内缩进。
  • 用空行分隔多个 CTE 以便视觉分组。
  • 最终的 SELECT 与 WITH 在同一级别开始。
WITH monthly_revenue AS (
    SELECT
        DATE_TRUNC('month', order_date) AS month,
        SUM(total_amount) AS revenue
    FROM orders
    WHERE order_date >= '2024-01-01'
    GROUP BY DATE_TRUNC('month', order_date)
),

revenue_growth AS (
    SELECT
        month,
        revenue,
        LAG(revenue) OVER (ORDER BY month) AS prev_revenue,
        ROUND(
            (revenue - LAG(revenue) OVER (ORDER BY month))
            / LAG(revenue) OVER (ORDER BY month) * 100,
            2
        ) AS growth_pct
    FROM monthly_revenue
)

SELECT
    month,
    revenue,
    prev_revenue,
    growth_pct
FROM revenue_growth
ORDER BY month;

8. 命名约定

一致的命名消除猜测并减少错误。

  • 表——使用 snake_case,复数名词:users、order_items、product_categories。
  • 列——使用 snake_case,描述性名称:created_at、is_active、total_amount。
  • 别名——使用有意义的短别名,而不是单个字母:ord 而不是 o,usr 而不是 u。例外:单字母在简单的两表 JOIN 中显而易见时。
  • 避免前缀——不要用表缩写前缀列名(tbl_users、col_name)。表上下文在查询中已经很清楚。
  • 布尔值——前缀使用 is_、has_ 或 can_:is_active、has_subscription、can_edit。
  • 主键——主键使用 id。外键应为 table_singular_id:user_id、order_id。
-- Good naming example
SELECT
    usr.user_id,
    usr.first_name,
    usr.is_active,
    ord.order_id,
    ord.total_amount,
    ord.created_at AS order_date
FROM users AS usr
INNER JOIN orders AS ord
    ON usr.user_id = ord.user_id
WHERE usr.is_active = TRUE
    AND usr.has_subscription = TRUE;

9. 差 vs 好:5 个前后对比

示例 1:简单 SELECT

:

select id,name,email,created_at from users where is_active=1 and role='admin' order by created_at desc limit 50;

:

SELECT
    id,
    name,
    email,
    created_at
FROM users
WHERE is_active = 1
    AND role = 'admin'
ORDER BY created_at DESC
LIMIT 50;

示例 2:多表 JOIN

:

select o.id,c.name,p.title,oi.qty,oi.price from orders o join customers c on o.customer_id=c.id join order_items oi on o.id=oi.order_id join products p on oi.product_id=p.id where o.status='completed' and o.created_at>='2024-01-01';

:

SELECT
    o.id AS order_id,
    c.name AS customer_name,
    p.title AS product_title,
    oi.qty,
    oi.price
FROM orders AS o
INNER JOIN customers AS c
    ON o.customer_id = c.id
INNER JOIN order_items AS oi
    ON o.id = oi.order_id
INNER JOIN products AS p
    ON oi.product_id = p.id
WHERE o.status = 'completed'
    AND o.created_at >= '2024-01-01';

示例 3:复杂 WHERE

:

select * from users where (role='admin' or role='moderator') and is_active=1 and (last_login>='2024-01-01' or created_at>='2024-06-01') and email like '%@company.com' order by last_login desc;

:

SELECT
    user_id,
    email,
    role,
    last_login,
    created_at
FROM users
WHERE (
        role = 'admin'
        OR role = 'moderator'
    )
    AND is_active = 1
    AND (
        last_login >= '2024-01-01'
        OR created_at >= '2024-06-01'
    )
    AND email LIKE '%@company.com'
ORDER BY last_login DESC;

示例 4:子查询

:

select u.name,u.email from users u where u.id in (select o.user_id from orders o where o.total>500 group by o.user_id having count(*)>3) and u.is_active=1;

:

SELECT
    u.name,
    u.email
FROM users AS u
WHERE u.id IN (
    SELECT o.user_id
    FROM orders AS o
    WHERE o.total > 500
    GROUP BY o.user_id
    HAVING COUNT(*) > 3
)
    AND u.is_active = 1;

示例 5:带聚合的 CTE

:

with user_totals as (select user_id,sum(amount) as total from payments where status='completed' group by user_id), user_ranks as (select user_id,total,rank() over (order by total desc) as rnk from user_totals) select u.name,ur.total,ur.rnk from user_ranks ur join users u on ur.user_id=u.id where ur.rnk<=10;

:

WITH user_totals AS (
    SELECT
        user_id,
        SUM(amount) AS total
    FROM payments
    WHERE status = 'completed'
    GROUP BY user_id
),

user_ranks AS (
    SELECT
        user_id,
        total,
        RANK() OVER (ORDER BY total DESC) AS rnk
    FROM user_totals
)

SELECT
    u.name,
    ur.total,
    ur.rnk
FROM user_ranks AS ur
INNER JOIN users AS u
    ON ur.user_id = u.id
WHERE ur.rnk <= 10;

10. SQL 代码检查器与格式化工具

自动化格式化以消除风格争论并确保一致性。

工具类型最适合
sqlfluffPython CLICI/CD 管道、团队标准化
pgFormatterPerl CLI / WebPostgreSQL 项目
sql-formatternpm packageNode.js / Web 应用

基于 Python 的代码检查/格式化工具。最全面的选项,有 60+ 可配置规则。支持多种 SQL 方言(PostgreSQL、MySQL、BigQuery、Snowflake)。

sqlfluff 用法:

# Install
pip install sqlfluff

# Lint a file
sqlfluff lint query.sql --dialect postgres

# Auto-fix formatting
sqlfluff fix query.sql --dialect postgres

# Configuration (.sqlfluff in project root)
[sqlfluff]
dialect = postgres
templater = raw
max_line_length = 120

[sqlfluff:rules:capitalisation.keywords]
capitalisation_policy = upper

[sqlfluff:rules:capitalisation.identifiers]
capitalisation_policy = lower

[sqlfluff:indentation]
indent_unit = space
tab_space_size = 4

基于 Perl 的格式化器,专注于 PostgreSQL。快速、简单,输出整洁。可作为 CLI 工具和 Web 界面使用。

pgFormatter 用法:

# Install (macOS)
brew install pgformatter

# Format a file
pg_format -u 1 -s 4 query.sql -o formatted.sql

# Options:
#   -u 1    : uppercase keywords
#   -s 4    : 4-space indent
#   -f 1    : function-style formatting
#   -W 5    : wrap after 5 columns in SELECT

JavaScript/npm 包。易于集成到 Node.js 项目、CI 管道和 Web 应用中。支持多种方言。

sql-formatter (npm) 用法:

// Install
npm install sql-formatter

// Usage in JavaScript/TypeScript
import { format } from 'sql-formatter';

const formatted = format(
  'select id,name from users where active=1',
  {
    language: 'postgresql',
    keywordCase: 'upper',
    tabWidth: 4,
    linesBetweenQueries: 2,
  }
);

console.log(formatted);
// SELECT
//     id,
//     name
// FROM
//     users
// WHERE
//     active = 1

11. IDE 设置

配置编辑器以自动格式化 SQL。

VS Code

安装 "SQL Formatter" 扩展(adpyke.vscode-sql-formatter)。添加到 settings.json:

// .vscode/settings.json
{
  "sql-formatter.uppercase": true,
  "sql-formatter.indent": "    ",
  "sql-formatter.linesBetweenQueries": 2,
  "[sql]": {
    "editor.defaultFormatter": "adpyke.vscode-sql-formatter",
    "editor.formatOnSave": true,
    "editor.tabSize": 4,
    "editor.insertSpaces": true
  }
}

DataGrip / JetBrains

DataGrip 有内置的 SQL 格式化。进入 Settings > Editor > Code Style > SQL 进行配置。关键设置:

Settings > Editor > Code Style > SQL:
  - Word Case: Keywords = UPPER
  - Word Case: Identifiers = lower
  - Tabs and Indents:
    - Use tab character: OFF
    - Tab size: 4
    - Indent size: 4
  - Wrapping:
    - Wrap long lines: ON
    - Place commas: After item (trailing)
  - Clauses:
    - Place SELECT on new line: ON
    - Place FROM on new line: ON
    - Place ON in JOIN on new line: ON

DBeaver

DBeaver 使用自己的格式化器。进入 Window > Preferences > SQL Editor > SQL Formatting。启用 "Insert spaces for tabs" 并将关键字大小写设为 UPPER。

Window > Preferences > SQL Editor > SQL Formatting:
  - Keyword case: UPPER
  - Indent type: Spaces
  - Indent size: 4
  - SELECT: Column list on new line
  - JOIN: ON clause on new line

Shortcut: Ctrl+Shift+F (format selected SQL)

12. 团队风格指南模板

复制并调整此模板以适合你的团队。将其作为 SQL_STYLE_GUIDE.md 存储在你的代码仓库中。

# SQL Style Guide

## Keywords
- UPPERCASE for all SQL keywords (SELECT, FROM, WHERE, JOIN, etc.)

## Indentation
- 4 spaces (no tabs)
- One clause per line (SELECT, FROM, WHERE, GROUP BY, ORDER BY)
- Sub-clauses indented one level (JOIN ON, AND, OR)

## SELECT
- One column per line
- Trailing commas
- Always alias computed columns

## FROM / JOIN
- Explicit JOIN syntax (never comma joins)
- Always use AS for table aliases
- ON clause indented under its JOIN
- Multi-condition ON: each AND on its own line

## WHERE
- AND/OR at the beginning of lines (not the end)
- Explicit parentheses for mixed AND/OR
- Subqueries indented as nested blocks

## CTEs
- Preferred over subqueries for readability
- Blank line between CTEs
- Final SELECT at the same level as WITH

## Naming
- Tables: snake_case, plural (users, order_items)
- Columns: snake_case, descriptive (created_at, is_active)
- Aliases: meaningful abbreviations (ord, usr, prod)
- Booleans: is_, has_, can_ prefix
- Primary keys: id
- Foreign keys: {table_singular}_id

## Enforcement
- Pre-commit hook: sqlfluff lint
- CI check: sqlfluff lint --dialect postgres
- Config: .sqlfluff in repo root

常见问题

SQL 格式化会影响查询性能吗?

不会。SQL 格式化纯粹是视觉上的。无论空格、换行或关键字大小写如何,数据库引擎都以相同方式解析和优化查询。为人类格式化,而不是为机器。

应该使用尾部逗号还是前导逗号?

两者都有效。尾部逗号(column1, column2,)更自然,更广泛采用。前导逗号(,column1 ,column2)更容易注释列。选择一种并保持一致。

如何在团队中强制执行 SQL 格式化?

在 CI/CD 管道中使用 sqlfluff 等自动化工具。添加运行格式化器的 pre-commit 钩子。记录你的风格指南并在仓库 README 中链接。自动化执行远比代码审查评论有效。

大写还是小写关键字——哪个更专业?

大写关键字是传统的行业标准,在没有语法高亮的环境(纯文本、邮件、文档)中效果最佳。小写在有良好语法高亮的现代开发环境中越来越流行。两者都不"更专业"——一致性比具体选择更重要。

如何格式化动态 SQL 或 ORM 生成的查询?

对于动态 SQL,像格式化普通查询一样格式化模板字符串。对于 ORM 生成的查询,大多数 ORM 有 .toSQL() 或 .toString() 方法——将输出通过格式化器处理以便调试。在日志中,使用 sqlfluff fix --stdin 等格式化器自动格式化捕获的查询。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

SQLSQL FormatterPSSQL to Prisma Schema{ }JSON Formatter

相关文章

SQL Join 详解:图解指南与实战示例

通过清晰的图表学习 SQL 连接。涵盖 INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL OUTER JOIN、CROSS JOIN 和自连接。

Prisma Schema 与关联关系指南

掌握 Prisma Schema 设计:模型、关联关系(1:1、1:N、M:N)、枚举、索引和迁移。常见数据库模式的实用示例。