SQL Diff: 2つのSQLファイルをオンラインで比較
2つのSQLクエリやスキーマファイルを貼り付け、フォーマットして横並びで比較。PostgreSQL、MySQL、SQL Server、SQLite、Oracleに対応。
SQL diffツールとは?
2つのSQLファイルを比較するための、ブラウザ内で動作する無料ツール。古いCREATE TABLEを左に、新しいものを右に貼り付けると、変更点が文字単位でハイライトされます。同じ流れで、分析クエリの2つのバージョン、ORMが出力したステートメント、pg_dumpの出力にも使えます。データはマシンの外に出ません。
diffはテキストレベルです。SQLをASTにパースしませんし、意味的な等価性も理解しません。SELECT a, bとSELECT b, aは、同じ行を別の順序で返すクエリでも、diffには別物に見えます。コードレビュー作業の95%(スキーマ移行、クエリのリファクタリング、ORM出力の比較)では、テキストdiffこそが実際に欲しいものです。なぜなら、カラムや句の順序はチームが変更を読み取る一部だからです。
400行の分析クエリで、行数が変わった原因となった新しいWHERE句を1つだけ探したことがあるなら、このツールは数秒でそこに連れていってくれます。SQLでない散文には、テキストdiffツールが適しています。JSON形式のORM結果ペイロードには、JSON diffがオブジェクトの並び替えをよりきれいに扱います。XMLメタデータとして書かれた古いDDLエクスポートには、XML diffが向いています。
diffの実際の動作
内部的にdiffは文字レベルで動作し、その後セマンティックパスがハイライトをずらして、ランダムな句読点ではなく識別子、キーワード、句の境界に乗るようにします。挿入は右側で緑、削除は左側で赤に表示されます。各ペインにはFormatボタンがあり、SQLを一貫したインデントと1ステートメント1行で整形してくれるので、比較前にフォーマットの雑音をほぼ消せます。
SQLは1つの言語ではなく、方言の集まりです。ISO/IEC 9075標準はコア文法を定義しますが、各データベースがそれを拡張します。PostgreSQLはドル引用文字列とRETURNINGを持ち、MySQLはバッククォート識別子とON DUPLICATE KEY UPDATEを、SQL ServerはTOPと角括弧識別子を使い、SQLiteはほぼ何でも受け入れ、OracleはROWNUMとCONNECT BYを持ちます。方言間でdiffを取っても問題ありません。ただし、ある方言で無効な断片があってもツールは警告しません。
もう1つ知っておくべきこと: クエリのテキストを比較することと、クエリのプランを比較することは別物です。テキストが同一の2つのステートメントが、統計の違いで大きく異なるプランを生むこともあれば、見た目が違う2つのステートメントが同じプランを生むこともあります。プランの比較には、psqlのEXPLAIN ANALYZEか、データベースクライアント(DataGrip、DBeaver、SSMS)の同等機能が適切なツールです。テキストのリファクタリングのレビューには、diffこそが欲しいものです。言語自体の背景はWikipediaにあります。
3ステップでSQLを比較する方法
テキスト2ペイン、diffひとつ。登録、アップロード、サーバー往復はありません。
- 1
SQLを貼り付けるかアップロード
古いSQLを左に、新しいSQLを右に貼り付け。または、左右どちらかでアップロードをクリックして、.sql、.ddl、.psqlファイルを直接読み込めます。Sampleボタンを押すと、両ペインに小さなordersスキーマの例が入り、ツールの動きを先に確認できます。
- 2
両側をフォーマットして公平に比較
各ペインでFormatをクリックすると、SQLが一貫したインデント、1ステートメント1行、キーワード大文字で整形されます。これで、片側がDataGripで整形されたクエリ、もう片側が手書きのクエリでも、空白や大文字小文字の違いではなく、本当のスキーマや句の変更がdiffでハイライトされます。
- 3
diffを読む
削除は左側に赤いハイライトで、挿入は右側に緑のハイライトで表示されます。片側をスクロールすると、もう片側も追従します。各ヘッダーの変更カウントは、カラム、JOIN、述語、DDL要素にまたがって、いくつの個別編集が見つかったかを示します。
SQL diffが正しいツールになる場面
適用前のスキーマ移行レビュー
移行PRが3カラムを追加し、2インデックスを調整します。前のCREATE TABLEと新しいものを貼り付けると、追加箇所が際立ちます: currency CHAR(3) NOT NULLカラムがDEFAULTなしで追加されており、これは既存行で失敗します。マイグレーションが本番に走る前にこれを捕まえること、デプロイのアラームが午前2時に鳴る後ではなく、それがこのワークフローの全意義です。VARCHAR(255)からVARCHAR(50)へカラム型が狭まるALTER TABLEでも同じ価値があります。
分析クエリの2バージョンのdiff
昨日12,400行を返したレポートクエリが今日は8,900行を返します。昨日保存したクエリと今日のものをdiffすれば、原因の変更が浮かびます: LEFT JOINがいつのまにかINNER JOINになっている、ダッシュボードを掃除しようとした誰かがWHERE status <> 'cancelled'を追加した、など。テキストdiffは数秒でその行に連れていってくれますが、Slackで両クエリを並べて読むなら10分かかるでしょう。
アプリのバージョン間でORM生成SQLを比較
HibernateやSQLAlchemyのアップグレード後、3つのJOINだったクエリが追加サブクエリ付きの4つになっていたり、パラメータバインディングが?から$1プレースホルダに切り替わっていたりします。各アプリバージョンのクエリログ(psqlのlog_statement = all、MySQLのスローログ、APMツール)からSQLをキャプチャしてdiffを取ります。エイリアス名は雑音だらけ(t1、t2、generatedAlias0)ですが、本当の構造変化が、diffで本物の編集としてハイライトされる唯一のものになるのが普通です。
DDLリファクタリングが意図を保つかの検証
user_ordersをordersにリネームし、JSONブロブカラムを正規化テーブルに分割しました。リファクタリング前後のpg_dump --schema-only出力を貼り付けると、すべての制約、インデックス、デフォルトを保てたかがdiffで明確になります。注意すべき罠は、リネーム中にこっそり消えたNOT NULLや、新テーブルで誰も再追加しなかったため落ちたUNIQUE制約です。
ステージングのスキーマが本番と一致するかの確認
ステージングと本番に対してpg_dump --schema-only --no-ownerを走らせ、その2つをdiffします。差分は次のデプロイで予定されているマイグレーションそのものになるはずです。それ以外の何か、余計なインデックス、別のカラム型、迷子のテストテーブル、はスキーマドリフトの兆候で、実際のデプロイで噛んできます。MySQLならmysqldump --no-data、SQL ServerならSCRIPT TO、あるいは任意のデータベースに対するDataGripのスキーマエクスポートで同じアプローチが取れます。
方言ポーティングのレビュー(PostgresからMySQL)
PostgreSQLからMySQLへ、あるいはその逆へクエリを移植するということは、変更が必要な関数、型、句を追跡することです: SERIAL対AUTO_INCREMENT、NOW()対CURRENT_TIMESTAMP、RETURNING対LAST_INSERT_ID()。元のクエリと移植後のバージョンをdiffすれば、方言の置換がすべて浮かびます。diffはターゲット方言でSQLを検証してはくれないので、確認のためpsqlやmysqlを通すのは依然必要ですが、行ごとのビューはどの判断を二重チェックすべきかを教えてくれます。
SQLクイックリファレンス
このdiffが最も頻繁に表面化させる方言とパースのエッジケースのための短いチートシート。ISO SQL標準と主要ベンダーのドキュメントに基づいています。
| Topic | What this tool does |
|---|
| 方言ドリフト | PostgreSQL、MySQL、SQL Server、SQLite、OracleはそれぞれISO SQL標準を独自構文で拡張しています。SERIAL、AUTO_INCREMENT、IDENTITY、GENERATED ALWAYS AS IDENTITYは同じ考えの4つの書き方です。 |
|---|
| 識別子の大小折りたたみ | PostgreSQLは引用されていない識別子を小文字に折りたたむので、Usersとusersは同じテーブルです。MySQLはlower_case_table_namesと背後のファイルシステムに依存します。SQL Serverはデフォルトで大小無視ですが、コレーションを尊重します。大小を保つには、識別子を"Users"(標準)、バッククォート(MySQL)、角括弧(SQL Server)で引用します。 |
|---|
| キーワードの大小 | SQLキーワードはどこでも大小無視なので、SELECTとselectはパーサにとって同じです。慣習はキーワードを大文字、識別子を小文字で、ほとんどのスタイルガイドとISO仕様がこれに従います。混在ケースは妥当で実行されますが、sqlfluffやsqlfmtのようなツールが正規化します。 |
|---|
| コメントスタイル | 2つの形式: -- 行コメント(行末で終了)と/* ブロックコメント */(標準SQLではネスト不可、ただし一部方言では許容)。MySQLは拡張として#による行コメントもサポートします。diffはコメントをテキストとして扱い、コメントの編集も他の変更と同じように表示します。 |
|---|
| ステートメント区切り | セミコロン(;)が標準SQLでステートメントを終了します。ほとんどのクライアントはスクリプト内でステートメント間にこれを要求します。一部のクライアント(SSMS)は代わりにGOをバッチセパレータとして使いますが、これはSQL文法の一部ではなく、クライアントディレクティブです。 |
|---|
| ドル引用文字列(PostgreSQL) | PostgreSQLは文字列リテラルに$tag$ ... $tag$をサポートしており、内側のシングルクォートをエスケープする必要がありません。関数本体に特に便利です。$$ SELECT 'hello' $$は妥当で、CREATE FUNCTIONブロックでよく使われます。他の方言はこの構文をパースしません。 |
|---|
| パラメータバインディングのプレースホルダ | 標準はありません。PostgreSQLとそれをターゲットにするORMは$1、$2を使います。JDBC、MySQL、ODBCは?を使います。名前付きプレースホルダ:nameはOracleや多くのORM文字列テンプレートでよく見られます。バージョン間のORM生成SQLは、プレースホルダのスタイルだけが違うことがよくあります。diff前に両側をフォーマットしてください。 |
|---|
| エンコーディング | 2026年現在、SQLファイルの普遍的なデフォルトはUTF-8です。古いWindows由来のスクリプトはまだUTF-16 LE + BOMで保存されていることがあり、これはテキストdiffの1行目に幽霊文字として現れます。2つのファイルが同一に見えるのにdiffが先頭の1文字変更を指摘するなら、BOMを疑い、明示的なUTF-8で保存し直してください。 |
|---|
SQL diff: よくある質問
両方のクエリでEXPLAIN ANALYZEを実行するのと何が違いますか?
スタックの違うレイヤーです。EXPLAIN ANALYZEはクエリのプラン、つまりオプティマイザが手元の統計を踏まえて何をすると決めたかを見せます。このツールはクエリのテキストのdiffを取ります。それがリポジトリで変わったものです。普通は両方が必要です: このdiffでリファクタリングを見つけ(JOINの種類が変わった、述語が動いた)、それからpsqlやクライアントでEXPLAIN ANALYZEを使ってオプティマイザが新しい形に対して妥当なプランを実際に選んだかを確認します。両者は補完関係で、置き換えではありません。
diffは方言を意識しますか(PostgreSQL対MySQL対SQL Server)?
いいえ。diffは入力をテキストとして扱うので、PostgreSQL、MySQL、SQL Server、SQLite、Oracleを別々にパースせず、方言互換でない構文にもフラグを立てません。これは意図的です: 方言間でdiffが取れる、ということで、これはまさにクエリ移植時に欲しい挙動です。方言を意識したリンティングが必要なら、SQLを別途sqlfluffやお使いのデータベースのパーサに通してください。レビュー目的(「このクエリで何が変わったか」)ではテキストレベルが正しい粒度です。
ツールはSQLをフォーマットしてくれますか?
はい、各ペインのFormatボタンがSQLを一貫したインデント、キーワード大文字、1ステートメント1行で整形します。基本的なフォーマッタであり、sqlfmtやsqlfluffの代替ではありません: 暗黙のJOINを明示的に書き換えませんし、特定の方言のスタイルガイドを強制もしません。狙いはdiff前にフォーマットの雑音を消すことです。片側がDataGripで整形され、もう片側が手書きでもきれいに比較できるように、ということです。
キーワードの大小(SELECT対select)は正規化されますか?
Formatボタンはキーワードを大文字にします。これはほとんどのスタイルガイドとISO SQL標準での慣習です。Formatをクリックしないと、混在ケースは差分として現れます。背後のエンジンが文字ベースだからです。SQLキーワードはどのデータベースでも大小無視なので、selectとSELECTは同じように実行されます。識別子の大小は別問題で、データベースによって異なります。Postgres、MySQL、SQL Serverの違いは下のクイックリファレンスを見てください。
ORMが出すt1、t2、generatedAlias0のようなエイリアスへの対処は?
ORM出力はそもそも雑音だらけです: HibernateはgeneratedAlias0、generatedAlias1を生成し、SQLAlchemyはanon_1、anon_2を使います。ORMの2バージョンが同じエイリアスを違う順序で割り当てると、テキストdiffはクエリが構造的に同一でも、すべてのエイリアス入れ替えを変更としてハイライトします。実用的な対処は、両側をフォーマットし、エイリアスだけの差分は無視し(同じ行の赤と緑のペアでだいたい一目でわかります)、本当の構造編集(新しいJOIN、違うWHERE句、削除されたカラム)に集中することです。
貼り付けるSQLにサイズ制限はありますか?
ソフトリミットはありますが、ハードリミットはありません。diffはブラウザで動くので、上限はブラウザのメモリと、どれだけ待つかの忍耐です。5,000行のpg_dump出力は、最近のラップトップなら1秒未満でdiffが取れます。20万行のダンプは数秒かかることがあり、ローエンド端末ではメモリ制限に当たるかもしれません。それほど大きなデータベースダンプには、まずスキーマだけdiff(pg_dump --schema-only)して、データレベルの比較が必要な場合に特定のテーブルへ掘り下げるのが正しい流れです。
プライバシーと仕組み
あなたのSQLはブラウザの外には出ません。フォーマッタもdiffもあなたのマシン上、ローカルで動きます。入力に対する分析、ログ、「お役立ち」クラウド往復はありません。これは、貼り付けるSQLに本物のテーブル名、本物のカラム名、サンプル行に本番データの実物が含まれているときに重要です。言語はISO/IEC 9075で文書化されており、方言リファレンスはPostgreSQL、MySQL、SQL Server、SQLiteです。