「MySQL向けに書いたSQLをSQL Serverに流したらLIMITが構文エラーになった」「PostgreSQLに移行したらバッククォートで囲んだテーブル名が全部エラーになった」—— DBを乗り換えたときに一度は経験するつまずきです。 同じ「上位10件だけ取得したい」という単純な処理なのに、DBが変わるだけで書き方がまるで変わります。 こうした「同じSQLのはずなのに動かない」現象の正体が、この記事で扱うSQL方言です。
本記事では、MySQL・PostgreSQL・SQL Serverの3製品を対象に、行数制限・文字列連結・自動採番・日付関数・識別子のクォートなど、 実務でつまずきやすい構文差を対応表で整理します。あわせて、DB移行やマルチDB対応で確認すべきチェックポイントも紹介します。
SQL方言とは — なぜ同じSQLなのに書き方が違うのか
SQLにはANSI(米国国家規格協会)・ISO(国際標準化機構)が定めた標準規格が存在し、 SELECT・FROM・WHERE・GROUP BY・JOINといった基本文法はMySQL・PostgreSQL・SQL Serverで共通です。 しかし標準規格だけではアプリケーション開発に必要な機能をすべて賄いきれないため、 各DB製品は独自の拡張構文・関数・データ型を追加してきました。
この「標準規格ではカバーされていない部分」こそが、DBを移行するときに書き換えが必要になる箇所です。 代表的なのが行数制限(LIMIT / TOP)・自動採番(AUTO_INCREMENT / SERIAL / IDENTITY)・文字列連結演算子・日付関数で、 いずれも各DBが標準化以前から独自の構文を発展させてきた歴史的経緯があります。 構文差を横断的に確認したい場合はSQL方言 構文比較・対応表で「やりたいこと」からキーワード検索できます。
つまずきやすい構文の対応表
まずは実務で書き換え漏れが起きやすい8項目を、MySQL・PostgreSQL・SQL Serverの3製品で比較します。
| 項目 | MySQL | PostgreSQL | SQL Server |
|---|---|---|---|
| 行数制限 | LIMIT n OFFSET m | LIMIT n OFFSET m | OFFSET m ROWS FETCH NEXT n ROWS ONLY |
| 文字列連結 | CONCAT(a, b) | a || b | a + b |
| NULL合体 | IFNULL(a, b) | COALESCE(a, b) | ISNULL(a, b) |
| 自動採番 | AUTO_INCREMENT | SERIAL / IDENTITY | IDENTITY(1,1) |
| 現在日時 | NOW() | NOW() | GETDATE() |
| 日付フォーマット | DATE_FORMAT(d, '%Y/%m/%d') | TO_CHAR(d, 'YYYY/MM/DD') | FORMAT(d, 'yyyy/MM/dd') |
| 識別子のクォート | バッククォート | ダブルクォート | 角括弧 [ ] |
| UPSERT | ON DUPLICATE KEY UPDATE | ON CONFLICT DO UPDATE | MERGE文 |
ここからは、特に移行時の事故につながりやすい項目を個別に深掘りします。
行数制限(LIMIT / TOP / FETCH FIRST)
MySQL・PostgreSQLは同じLIMIT-OFFSET構文(SQL標準ではなく両DB共通の拡張構文)を共有しているため、 この2つの間での書き換えはほぼ不要です。 一方SQL ServerはLIMIT句自体をサポートしておらず、独自のTOP句か、 SQL標準(SQL:2008)で定められたOFFSET-FETCH句のどちらかを使う必要があります。 TOP nは単に先頭n件を返すだけでOFFSET相当の指定ができないため、ページング処理を実装する場合は ORDER BYを必須とするOFFSET-FETCH句を使うのが基本です。
-- MySQL / PostgreSQL: 21件目から10件取得 SELECT * FROM users LIMIT 10 OFFSET 20; -- SQL Server: ORDER BY が必須 SELECT * FROM users ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
自動採番(AUTO_INCREMENT / SERIAL / IDENTITY)
主キーの自動採番はDDL(テーブル定義)レベルで構文が完全に異なるため、移行時に見落としやすい項目です。 MySQLはカラム定義にAUTO_INCREMENTを付けるだけですが、 PostgreSQLはSERIAL型(内部的にはシーケンス+DEFAULT値の組み合わせ)か、 SQL標準に沿ったGENERATED ALWAYS AS IDENTITYを使います。 SQL ServerはIDENTITY(開始値, 増分値)という独自構文です。 単純な文字列置換では対応できないため、CREATE TABLE文は1つずつ目視で確認するのが安全です。
識別子のクォートと大文字小文字の扱い
order・user・groupのような予約語をテーブル名やカラム名にそのまま使っている場合、DBによってエスケープに使う記号が異なります。 MySQLはバッククォート、PostgreSQLはダブルクォート、SQL Serverは角括弧です。
-- MySQL: バッククォートでエスケープ SELECT `order`, `user` FROM `table`; -- PostgreSQL: ダブルクォートでエスケープ SELECT "order", "user" FROM "table"; -- SQL Server: 角括弧でエスケープ SELECT [order], [user] FROM [table];
さらに厄介なのが大文字小文字の扱いです。PostgreSQLはクォートなしの識別子を自動的に小文字へ変換して扱うため、Usersと書いても内部的にはusersとして扱われます。 MySQLはOSや設定次第でテーブル名の大文字小文字を区別することがあり、SQL Serverはデータベースの照合順序(Collation)設定に依存します。 複数の環境をまたいで開発する場合は、識別子をあらかじめ小文字で統一しておくと予期しない不一致を避けられます。
UPSERT(INSERT or UPDATE)の構文差
「存在すれば更新、なければ挿入」という頻出パターンも、DBごとに全く異なる構文になります。 機械的な置換は効かないため、移行時は必ず個別にテストする必要がある項目です。
-- MySQL INSERT INTO t (id, name) VALUES (1, 'A') ON DUPLICATE KEY UPDATE name = VALUES(name); -- PostgreSQL INSERT INTO t (id, name) VALUES (1, 'A') ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name; -- SQL Server MERGE INTO t AS target USING (VALUES (1, 'A')) AS src (id, name) ON target.id = src.id WHEN MATCHED THEN UPDATE SET name = src.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (src.id, src.name);
データ型・トランザクション構文の違い(補足)
行数制限や自動採番ほど頻出ではありませんが、移行時に地味にハマりやすいのがデータ型と制御構文です。 たとえば真偽値型は、PostgreSQLだけが真のBOOLEAN型を持ち、MySQLはTINYINT(1)、SQL ServerはBIT型で代替しています。
トランザクションの開始キーワードも異なり、MySQLはSTART TRANSACTION、 PostgreSQLはBEGIN、 SQL ServerはBEGIN TRANSACTIONが標準的な書き方です。 COMMIT・ROLLBACKのキーワードは3製品で共通なので、開始部分だけ注意すれば問題ありません。
移行・共存時のチェックリスト
DB移行や複数DB対応のプロジェクトで、実際に確認・テストしておくべきポイントを整理しました。
- 自動採番構文の書き換え — AUTO_INCREMENT・SERIAL・IDENTITYはDDL全体で構文が異なるため、 CREATE TABLE文を1つずつ目視レビューする
- LIMIT / TOP / OFFSET-FETCHの変換 — SQL Server移行時はページング処理にORDER BYの追加が必須になる
- UPSERT構文の書き直し — ON DUPLICATE KEY UPDATE・ON CONFLICT・MERGEは動作も構文も異なるため、個別にテストケースを用意する
- 識別子のクォートと予約語の確認 — order・user・groupなどの予約語をカラム名に使っている場合、移行先でクォート記号を書き換える
- 大文字小文字の運用統一 — 開発環境と本番でDB製品が異なる場合、識別子は小文字に統一しておくと事故を防げる
- 移行前後のスキーマDDL比較 — 移行前後で出力したCREATE TABLE文をテキスト差分比較ツールで比較し、意図しない差異が紛れ込んでいないか確認する
- データ移行経路の確認 — CSVでエクスポートしたデータをAPI連携やドキュメント指向DBに投入する場合は、CSV↔JSON変換ツールでフォーマットを変換すると手軽に検証できる
まとめ
- SQLにはANSI/ISO標準があるが、各DB製品が独自拡張を持つため「SQL方言」が生まれる
- 行数制限はMySQL/PostgreSQLがLIMIT-OFFSET、SQL ServerはOFFSET-FETCH(ORDER BY必須)またはTOP
- 文字列連結はMySQL=CONCAT()、PostgreSQL=||、SQL Server=+ で構文が異なる
- 自動採番はAUTO_INCREMENT / SERIAL・IDENTITY / IDENTITY(1,1)とDDLレベルで完全に別構文
- 識別子のクォートはバッククォート・ダブルクォート・角括弧の3通り、大文字小文字の扱いも製品ごとに違う
- UPSERT(ON DUPLICATE KEY UPDATE / ON CONFLICT / MERGE)は機械的置換が効かないため個別テストが必須
- 移行時はDDL・UPSERT・ページング処理を重点的に確認し、より詳細な構文差はSQL方言 構文比較・対応表で横断検索できる