はじめに
データベースを扱う開発者なら誰もが経験するのが、未フォーマットのSQLの壁です。キーワードはすべて小文字、改行なし、テーブル名とJOIN条件が一体になって、ひたすら右に続く一行のコード。デバッグはほぼ不可能で、プルリクエストでのレビューもできず、同僚への引き継ぎも困難です。SQLフォーマットは単なる見た目の問題ではありません。可読性の向上、バグの削減、チーム標準の統一、そして保守性の大幅な改善をもたらすプロフェッショナルな規律です。
シンプルな5行の SELECT 文であれ、CTEとウィンドウ関数を含む100行の分析パイプラインであれ、一貫したフォーマットこそが、コードが意図を伝えるか隠すかを決める鍵となります。
SQLの歴史
SQLは1970年代初頭、IBMのサンノゼ研究所で誕生しました。Edgar F. Coddのリレーショナルモデルに触発されたDonald D. ChamberlinとRaymond F. BoyceがSEQUEL(Structured English Query Language)という言語を開発し、後にSQLと改名されました。IBMはSystem Rにおいて初めてこれを実装し、Oracleが1979年に商用SQLデータベースを初めて販売しました。
米国規格協会(ANSI)は1986年に最初のSQL標準を発行し、その後ISOが批准しました。後続の改訂版として、SQL-89、SQL-92、SQL:1999、SQL:2003、SQL:2008、SQL:2011、SQL:2016が順次リリースされ、トリガー、ストアドプロシージャ、再帰クエリ(CTE)、ウィンドウ関数、JSONサポートなどの機能が追加されました。
標準が存在する一方で、主要なデータベースベンダーは独自の拡張機能を導入し、それぞれの方言を生み出しました。MySQL、PostgreSQL、Microsoft SQL Server(T-SQL)、SQLite、Oracle PL/SQLなどがその例です。これらの方言は共通のコアを共有していますが、文字列操作、日付関数、自動採番列などの構文において違いがあります。
SQL構文の基礎
フォーマットを理解する前に、基本的な構成要素を把握しておくと役立ちます。標準的な SELECT 文は以下の論理的な順序で構成されます:
SELECT -- 返す列
FROM -- ソーステーブル
JOIN -- 関連テーブル
WHERE -- 行レベルのフィルター
GROUP BY -- 集計グループ化
HAVING -- 集計レベルのフィルター
ORDER BY -- ソート順
LIMIT -- 行数制限
各句には明確な目的があります。WHERE は集計前にフィルタリングし、HAVING は集計後にフィルタリングします。GROUP BY は複数の行をグループごとの1行に折りたたみます。ORDER BY は最後に(LIMIT の前に)適用されます。この順序を理解することで、各句が独立した行から始まるようにクエリをフォーマットでき、論理的な流れが一目でわかるようになります。
フォーマット規約
単一の普遍的な標準はありませんが、以下の規約が広く採用されています:
キーワードの大文字化
多くのスタイルガイドでは、SQLキーワード(SELECT、FROM、WHERE、JOIN、GROUP BY、HAVING、ORDER BY、LIMIT)を大文字で記述することを推奨しています。これにより、構造的なキーワードとユーザー定義の識別子の間に視覚的なコントラストが生まれます。一部の現代的なチームでは美観のために小文字を好み、dbtなどのツールがこのスタイルを普及させています。重要なのは一貫性を保つことです。
インデント
列リスト、ON 句、WHERE 述語のインデントには4スペース(またはコンパクトスタイルでは2スペース)を使用します。タブはエディターによってレンダリングが異なるため、共有コードベースでは避けるべきです。
改行
各主要句(SELECT、FROM、WHERE、GROUP BY など)は新しい行から始めるべきです。SELECT の列リストは、キーワードの下にインデントして1列ずつ記述します。これにより、個々の列の追加、削除、コメントアウトが容易になります。
カンマの位置 2つの流派があります:末尾カンマ(行末)と先頭カンマ(次の行の先頭)。末尾カンマはほとんどのプログラマーにとって自然ですが、先頭カンマは欠落したカンマを一目で発見しやすくします。どちらかを選んで一貫して使用しましょう。
エイリアス
エイリアスを付ける際は常に AS キーワードを使用します(u user ではなく u AS user)。これにより意図が明確になり、複雑なクエリでの曖昧さが排除されます。
フォーマット前後の比較例
-- フォーマット前(未整形)
select u.id,u.name,o.total from users u inner join orders o on u.id=o.user_id where o.total>100 and u.active=1 order by o.total desc limit 10
-- フォーマット後
SELECT
u.id,
u.name,
o.total
FROM
users u
INNER JOIN orders o ON u.id = o.user_id
WHERE
o.total > 100
AND u.active = 1
ORDER BY
o.total DESC
LIMIT 10
フォーマット後のバージョンは、テーブルの関係、フィルター条件、ソート順を即座に把握できます。
SQL方言の比較
SQLは標準化されていますが、方言間で重要な違いがあります:
| 機能 | MySQL | PostgreSQL | SQL Server | SQLite |
|---|---|---|---|---|
| 文字列結合 | CONCAT() |
|| または CONCAT() |
+ または CONCAT() |
|| |
| 自動採番 | AUTO_INCREMENT |
SERIAL / IDENTITY |
IDENTITY |
AUTOINCREMENT |
| 行数制限 | LIMIT n |
LIMIT n |
TOP n |
LIMIT n |
| 日付関数 | DATE(), NOW() |
CURRENT_DATE, NOW() |
GETDATE() |
DATE() |
| 大文字小文字 | デフォルト:非感受性 | デフォルト:感受性あり | デフォルト:非感受性 | 設定可能 |
データベース間でクエリを移植する際、これらの違いを理解することが重要です。対象方言を認識するフォーマッターは、方言固有の構文に対しても構文的に有効な出力を生成できます。
SQLスタイルガイド
チームが合意を形成するのに役立つ2つの広く参照されているスタイルガイドがあります:
Simon HolywellのSQL スタイルガイド(sqlstyle.guide)
このガイドは、ルートワードの整列、空白の「川」を使った句の視覚的分離、先頭カンマ、識別子へのsnake_caseの使用を強調しています。主観的ですが、内容は徹底的であり、データエンジニアリングチームで広く使用されています。
Google SQLスタイルガイド
Googleの内部スタイルガイド(BigQueryドキュメントを通じて一部公開)は、末尾カンマ、4スペースインデント、大文字キーワード、長い IN リストの折り返しを好みます。多くのGoogleエンジニアが慣れ親しんでいるJavaコーディング標準とよく一致しています。
ほとんどのチームはこれらのいずれかから軽量な内部スタイルガイドを作成し、CIパイプラインでの自動フォーマットによって強制します。
SQLフォーマッターの仕組み
SQLフォーマッターは本質的に特殊化されたコンパイラーのフロントエンドです。処理は通常3つの段階を経ます:
1. 字句解析(Tokenization)
生のSQL文字列がフラットなトークンストリームに分解されます:キーワード(SELECT、FROM)、識別子(users、u)、演算子(=、>)、リテラル('completed'、100)、区切り文字(,、;)、空白/コメント。字句解析器は構造を理解しません — 文字を分類するだけです。
2. 構文解析(Parsing)
トークンストリームが抽象構文木(AST)を構築するパーサーに入力されます。ASTの各ノードは論理的な構造を表します:SelectStatement ノードには ColumnList ノード、FromClause ノード、WhereClause ノードなどが含まれます。パーサーは文法規則を適用し、構文エラーを検出します。
3. 再出力(Pretty Printing) フォーマッターはASTを走査し、フォーマット規則に従ってSQLテキストを出力します:句キーワード後の改行、子ノードのインデント、演算子周囲のスペース、キーワードの大文字変換。出力がASTから派生するため、クエリの意味は完全に保持されます。
より単純なフォーマッターの中には、完全なAST構築をスキップし、トークンストリームに直接ルールベースの変換を適用するものもあります。この方法は高速ですが、複雑なネスト構造に対する精度は低下します。
一般的なSQLパターンと例
複数テーブルJOIN
SELECT
c.name AS customer_name,
p.name AS product_name,
oi.quantity,
oi.unit_price
FROM
customers c
INNER JOIN orders o ON c.id = o.customer_id
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
WHERE
o.status = 'shipped'
AND o.created_at >= '2024-01-01'
ORDER BY
c.name,
o.created_at DESC;
サブクエリ
SELECT
department,
AVG(salary) AS avg_salary
FROM employees
WHERE salary > (
SELECT AVG(salary)
FROM employees
)
GROUP BY department
ORDER BY avg_salary DESC;
CTE(共通テーブル式)
WITH monthly_revenue AS (
SELECT
DATE_TRUNC('month', created_at) AS month,
SUM(amount) AS revenue
FROM orders
WHERE status = 'completed'
GROUP BY 1
),
ranked_months AS (
SELECT
month,
revenue,
RANK() OVER (ORDER BY revenue DESC) AS rank
FROM monthly_revenue
)
SELECT *
FROM ranked_months
WHERE rank <= 3;
CTEは、複雑なクエリを名前付きの再利用可能なビルディングブロックに分解することで可読性を向上させます。中間集計がさらなる計算に使用される分析SQLで特に価値があります。
ウィンドウ関数
SELECT
employee_id,
department,
salary,
AVG(salary) OVER (PARTITION BY department) AS dept_avg,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees;
ウィンドウ関数(OVER を伴う ROW_NUMBER、RANK、LAG、LEAD、SUM、AVG)はSQLの最も強力な機能の一つです。適切なフォーマット — 各 OVER 句をその関数と同じ行に、PARTITION BY と ORDER BY が明確に見える形で — は可読性に不可欠です。
HAVINGを使った集計
SELECT
category,
COUNT(*) AS total_products,
AVG(price) AS avg_price,
MAX(price) AS max_price
FROM products
WHERE active = 1
GROUP BY category
HAVING COUNT(*) > 10
ORDER BY avg_price DESC;
IDEおよびツールとの統合
VS Code
sql-formatter npmパッケージは複数のVS Code拡張機能を支えています。settings.json でファイル保存時のフォーマットを設定できます:
"[sql]": { "editor.defaultFormatter": "mtxr.sqltools" }
JetBrains DataGrip
DataGripには コード → コードの再フォーマット(Ctrl+Alt+L)の下に組み込みのSQLフォーマット機能があります。スタイルオプションは 設定 → エディター → コードスタイル → SQL でデータソース方言ごとに設定できます。
DBeaver DBeaverは SQLエディター → SQLのフォーマット を通じてSQLフォーマットをサポートし、設定 → エディター → SQLエディター → フォーマット でカスタムフォーマット設定が可能です。
コマンドライン
sql-formatter npmパッケージはCLIツールとして使用できます:
npx sql-formatter --language postgresql --indent 4 query.sql
ベストプラクティス
- 早めにフォーマット、こまめにフォーマット。 コードレビューの前だけでなく、毎回のコミット前にフォーマッターを実行しましょう。
- まずスタイルガイドで合意する。 チームの規約に関するコンセンサスをフォーマッターで代替することはできません。
- エイリアスを一貫して使用する。 短いエイリアス(1-2文字)はシンプルなクエリに適しています;複雑なクエリでは説明的なエイリアスが可読性を向上させます。
- CTEに説明的な名前をつける。
cte1やtmpよりmonthly_revenueの方がはるかに優れています。 - 複雑なロジックにコメントをつける。 自明でないビジネスロジックを説明するインラインコメントがついた、適切にフォーマットされたクエリは、巧妙だが無言のSQLよりもはるかに保守しやすいです。
- CIでSQLをリントする。
sqlfluffなどのツールはプルリクエストのパイプラインで自動的にフォーマット規則を適用できます。 - CASE WHENを読みやすく保つ。 長い
CASE WHENチェーンは複数行に分割し、各ブランチを1行に収めましょう。 - すべての列参照を修飾する。 複数テーブルのクエリでは、常にテーブルエイリアスで列名をプレフィックスします(
idだけでなくu.id)。
よくある質問
フォーマットするとクエリの動作が変わりますか? 変わりません。フォーマッターは空白、改行、キーワードの大文字小文字のみを変更します。論理構造と実行計画は変わりません。
フォーマッターは構文エラーを修正できますか?
できません。フォーマッターは正しく解析するために構文的に有効な(またはほぼ有効な)SQLが必要です。エラーを検出して説明するには sqlfluff などのリンターを使用してください。
どのキーワードケーススタイルを使うべきですか? 大文字のキーワードがSQLコミュニティやスタイルガイドで最も伝統的かつ広く推奨される規約です。ただし、dbtや現代のデータツールでは小文字がますます普及しています。チームで選択し、自動的に適用しましょう。
ストアドプロシージャとPL/SQLブロックはサポートされていますか?
サポートの程度は様々です。ほとんどのフォーマッターはDML(SELECT、INSERT、UPDATE、DELETE)を適切に処理します。手続き型拡張(PL/SQL、T-SQLバッチ、BEGIN...END ブロック)は方言対応のパーサーが必要で、サポート品質にばらつきがあります。
オンラインフォーマッターを使用する際、SQLデータは安全ですか? 優れたオンラインフォーマッターは、JavaScriptを使用してブラウザ上でクライアントサイドですべてを処理します。クエリがあなたのマシンを離れることはありません。機密なスキーマ情報を貼り付ける前に、ツールのプライバシーポリシーでこれを確認してください。
フォーマッターとリンターの違いは何ですか?
フォーマッターは意味を変えずにSQLの表現を変換します。リンター(sqlfluff、SQLCheck)はスタイル違反、アンチパターン、潜在的なバグについてSQLを分析し、自動修正ではなく問題を報告します。