ぱんだツールズぱんだツールズ

技術背景

MySQL・PostgreSQL・SQL Serverの違い — SQL方言対応表と移行の注意点

約7分

「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製品で比較します。

項目MySQLPostgreSQLSQL Server
行数制限LIMIT n OFFSET mLIMIT n OFFSET mOFFSET m ROWS FETCH NEXT n ROWS ONLY
文字列連結CONCAT(a, b)a || ba + b
NULL合体IFNULL(a, b)COALESCE(a, b)ISNULL(a, b)
自動採番AUTO_INCREMENTSERIAL / IDENTITYIDENTITY(1,1)
現在日時NOW()NOW()GETDATE()
日付フォーマットDATE_FORMAT(d, '%Y/%m/%d')TO_CHAR(d, 'YYYY/MM/DD')FORMAT(d, 'yyyy/MM/dd')
識別子のクォートバッククォートダブルクォート角括弧 [ ]
UPSERTON DUPLICATE KEY UPDATEON CONFLICT DO UPDATEMERGE文

ここからは、特に移行時の事故につながりやすい項目を個別に深掘りします。

行数制限(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方言 構文比較・対応表で横断検索できる

よくある質問

SQL方言とは何ですか?

ANSI/ISO SQL標準で共通の文法(SELECT・FROM・WHERE・GROUP BY・JOINなど)は決まっていますが、その標準だけでは実務に必要な機能(自動採番・文字列関数・日付処理など)を賄いきれないため、MySQL・PostgreSQL・SQL Serverなど各データベース製品が独自の拡張構文や関数を追加しています。この「同じ目的なのに製品ごとに書き方が違う」状態を指して「SQL方言」と呼びます。基本文法は共通でも、行数制限・文字列連結・自動採番・日付関数などはほぼ確実に書き換えが必要になります。

MySQLで書いたSQLをそのままPostgreSQLに移行できますか?

単純なSELECT文であればそのまま動くことも多いですが、実際にはAUTO_INCREMENT(MySQL)をSERIALまたはGENERATED ALWAYS AS IDENTITY(PostgreSQL)に、バッククォートによる識別子エスケープをダブルクォートに、DATE_FORMAT()をTO_CHAR()に書き換える必要があります。特にON DUPLICATE KEY UPDATE(MySQLのUPSERT構文)はPostgreSQLのON CONFLICT DO UPDATEと構文自体が異なるため、機械的な文字列置換では対応できず、該当箇所を個別に書き直す必要があります。

SQL ServerでLIMIT句が使えないのはなぜですか?

LIMIT句はSQL標準ではなくMySQL・PostgreSQLの拡張構文で、SQL Serverはこれに対応していません。SQL Serverでは独自のTOP句か、SQL標準(SQL:2008)由来のOFFSET-FETCH句を使います。TOP nは先頭n件を取得するだけの構文でOFFSET指定ができないため、ページング(〇件目から〇件取得)を実装する場合はORDER BYを必須条件としたOFFSET m ROWS FETCH NEXT n ROWS ONLYを使う必要があります。この構文はSQL Server 2012以降で対応しています。

複数のDBに対応したSQLを書くにはどうすればいいですか?

アプリケーション側でORM(Prisma・TypeORM・SQLAlchemyなど)やクエリビルダーを使うと、CRUD操作の大部分はDBの違いを自動的に吸収してくれます。ただし複雑な集計クエリやパフォーマンスチューニングで生のSQLを書く場面では方言差を意識する必要があります。移行や複数DB対応が必要な場合は、まず対応表で差分を洗い出したうえで、実際に各DB上でクエリを実行して動作確認するのが確実な進め方です。

ORMを使えば方言の違いを完全に気にしなくてよいですか?

基本的なCRUD操作はORMが方言差を吸収しますが、生SQLを書くケース(複雑な集計・ウィンドウ関数・DB固有機能の利用)では方言の違いが直接影響します。またORMのマイグレーション機能も、自動採番やデータ型の表現方法まで完全に隠蔽できるとは限りません。マイグレーション実行後は生成されたCREATE TABLE文を確認し、意図した型・制約になっているかをチェックすることをおすすめします。

テーブル名やカラム名のクォート文字はDBごとにどう違いますか?

識別子(テーブル名・カラム名)をエスケープする文字はDBごとに異なり、MySQLはバッククォート(`name`)、PostgreSQLはダブルクォート("name")、SQL Serverは角括弧([name])を使います。order・user・groupのような予約語をそのままカラム名に使っている場合や、大文字小文字を区別させたい場合にこのクォートが必要になります。移行時にクォート文字を書き換え忘れると構文エラーになりやすい部分です。

テーブル名やカラム名の大文字小文字の扱いはDBごとに違いますか?

はい、大きく異なります。PostgreSQLはクォートなしの識別子を自動的にすべて小文字へ変換して扱います。MySQLはOSやlower_case_table_namesという設定値によって挙動が変わり、Linux環境ではテーブル名の大文字小文字を区別するのが標準的です。SQL Serverはデータベースの照合順序(Collation)設定に依存し、多くの日本語環境では大文字小文字を区別しない設定がデフォルトになっています。複数環境で開発する場合は、識別子を小文字に統一しておくと事故を防げます。

この検索ツールで調べたSQL構文の内容はサーバーに送信されますか?

ぱんだツールズのSQL方言 構文比較・対応表ツールは、キーワード検索や構文表示の処理も含めてすべてブラウザ内のJavaScriptだけで完結します。入力した検索キーワードや閲覧した構文がサーバーに送信されることはないため、社内システムの移行検討やDB選定など、外部に出しにくい情報を扱う場面でも安心して利用できます。

この記事で紹介したツール