
INSERTステートメントを手動で書くのをやめよう:JSON to SQLコンバーターの使い方
📷 Christina Morillo / PexelsINSERTステートメントを手動で書くのをやめよう:JSON to SQLコンバーターの使い方
200行のJSONエクスポートをINSERTステートメントに手動で変換するのは苦痛でエラーが起きやすい。自動化する方法と注意点を解説します。
サードパーティのサービスから200行のユーザーデータをエクスポートしました。フォーマットはJSONです。データベースはSQL INSERTステートメントを必要としています。そして、なぜかそれを手動で書くことを考えています。
その思考過程はおおよそ次のようになります:JSONファイルを開き、構造を確認し、CREATE TABLEステートメントを書き、それからINSERTステートメントを始める。最初の1つに2分かかる。2番目は1分。10番目には何か仕組みができてくる。50番目には制約エラーでインポートが失敗するまで気づかないタイポが混入している。
もっと良い方法があります。JSON to SQLコンバーターはJSONの配列を受け取り、スキーマを推測し、実行可能なSQLを生成します — バルクのINSERTステートメントと、正しいカラム名と引用符付きの値を含めて。このガイドでは、ツールが何をするか、効果的な使い方、そして手動でのクリーンアップが必要な場所について説明します。
ツールが実際に行うこと
概要として:JSONオブジェクトの配列を貼り付け、SQLダイアレクトを選択すると、ツールは2つのものを生成します — 推測されたスキーマに基づくCREATE TABLEステートメントと、データのための一連のINSERTステートメント。
型推測が面白い部分です。ツールはJSONの値を見て、SQL型について合理的な推測を行います。文字列はVARCHARまたはTEXTになります。数値はINTまたはFLOATになります。ブーリアンはダイアレクトに応じてBOOLEANまたはTINYINT(1)になります。null値はデフォルトでnullableなVARCHARになります。
またSQLダイアレクト間のフォーマットの違いも自動的に処理します — MySQLのバックティッククォートのカラム名、PostgreSQLのダブルクォートの識別子、SQL ServerのテキストカラムのNVARCHAR。
現実的な例
SaaSアプリからJSONエクスポートがあるとします。ユーザーテーブルのエクスポートは次のようになります:
[
{
"id": 1,
"name": "Alice Chen",
"email": "alice@example.com",
"age": 31,
"active": true,
"created_at": "2025-01-15T09:23:00Z"
},
{
"id": 2,
"name": "Ben Kowalski",
"email": "ben@example.com",
"age": 28,
"active": false,
"created_at": "2025-02-03T14:55:00Z"
},
{
"id": 3,
"name": "Sara Okonkwo",
"email": "sara@example.com",
"age": 34,
"active": true,
"created_at": "2025-03-11T08:00:00Z"
}
]
これを貼り付けてMySQLを選択すると、出力は次のようになります:
CREATE TABLE `users` (
`id` INT,
`name` VARCHAR(255),
`email` VARCHAR(255),
`age` INT,
`active` BOOLEAN,
`created_at` VARCHAR(255)
);
INSERT INTO `users` (`id`, `name`, `email`, `age`, `active`, `created_at`) VALUES
(1, 'Alice Chen', 'alice@example.com', 31, TRUE, '2025-01-15T09:23:00Z'),
(2, 'Ben Kowalski', 'ben@example.com', 28, FALSE, '2025-02-03T14:55:00Z'),
(3, 'Sara Okonkwo', 'sara@example.com', 34, TRUE, '2025-03-11T08:00:00Z');
200行の場合も同じ構造で、200個の値のバッチが得られます。これは貼り付けとクリックで30秒程度の作業であり、15分のエラーが起きやすいタイピングとは比べものになりません。
この出力でいくつかのことに注目してください。created_atフィールドはDATETIMEではなくVARCHAR(255)として型付けされています — これが重要な理由は後で説明します。テーブル名はデフォルトでusersになっており、ツールで指定できる場合もあります。CREATE TABLEにはPRIMARY KEYやNOT NULL制約が含まれていません — それはあなたの仕事です。
知っておくべきダイアレクトの違い
「SQLダイアレクト」セレクターは見た目以上に重要です。データベース間の構文の違いは小さいですが、間違ったものを選ぶとエラーが発生します。
MySQLは識別子のクォートにバックティックを使用します:`column_name`。これは、カラム名がMySQLの予約語(order、key、index、valuesなど)と衝突する場合に重要です。MySQLはまた、古いスキーマではブーリアンにTINYINT(1)を使用しますが、MySQL 5.7+ではBOOLEANも動作します。
PostgreSQLは識別子にダブルクォートを使用します:"column_name"。ネイティブのBOOLEANとTIMESTAMP WITH TIME ZONE型があります。また、自動インクリメントの主キーにSERIALとBIGSERIALがあり、MySQLのAUTO_INCREMENTとは異なります。
SQLiteは型に対して寛容です — SQLiteは厳格な型ではなく「型アフィニティ」を使用するため、TEXTとして宣言されたカラムに整数を保存することもできます。SQLiteの出力ではTEXTやINTEGERのようなシンプルな型名がよく見られます。
SQL ServerはUnicodeテキスト文字列にVARCHARの代わりにNVARCHARを使用し、識別子にスクエアブラケットのクォートを使用します:[column_name]。SQL Serverはまたブーリアン値にBOOLEANやTINYINTではなくBITを使用します。
特定のデータベースにインポートする場合は、そのダイアレクトを選択してください。SQLを手動で実行して適応させる場合は、どのダイアレクトでも使えるスタート地点が得られますが、変更が必要な点に注意してください。
型推測:正しくいける場合と失敗する場合
ツールは一般的なケースに対してかなり優秀です:
- JSONの
123はSQLのINTになる 123.45はFLOATまたはDECIMALになる"hello"はVARCHAR(255)またはTEXTになるtrue/falseはBOOLEANまたはTINYINT(1)になるnullはnullableなカラムになり、通常VARCHARとして型付けされる
失敗するのは、JSONが文字列を使用して非文字列データを表現している場所です。
電話番号は典型的な落とし穴です。JSONに"phone": "12345"がある場合、これはJSONの文字列であり、VARCHAR(255)が得られます — 電話番号は文字列として保存すべきなので正しいです(先頭の0、+付きの国コード、フォーマットされた番号)。しかしソースシステムで電話番号が整数として保存されていた場合、"phone": 12345が表示されてINTになるかもしれません。そうするとインポートが先頭の0とフォーマットをサイレントに削除します。
日付と時刻のフィールドはJSONがネイティブの日付型を持たないため、ほとんどの場合VARCHARになります。"2025-01-15T09:23:00Z"はJSONに関する限り単なる文字列です。適切なDATETIMEまたはTIMESTAMPカラムが必要な場合は、生成後にCREATE TABLEステートメントのカラム型を変更してください。データ自体はTIMESTAMPカラムへの挿入時にSQLがISO 8601文字列を解析するため、正しくインポートされます。
数値のように見えるIDはより扱いが難しいです。値が1042のidフィールドはINTとして型付けされます。それが問題ない場合もありますが、システムがサンプルの最初のいくつかの行でたまたますべて数値のUUIDや英数字IDを使用している場合もあります。201行目にid: "a1b2c3"があると、インポートが失敗します。
大きなテキストフィールドはVARCHAR(255)として型付けされます。フィールドに長いコンテンツ(記事本文、説明、シリアライズされたデータ)が含まれている場合は、MySQLのTEXTやLONGTEXT、またはPostgreSQLのTEXT(長さ制限なし)に型を変更する必要があります。
経験則として:実行する前に常に生成されたCREATE TABLEステートメントを確認してください。INSERTステートメントは通常問題ありませんが、スキーマ推測は人間の判断が必要な部分です。
バッチインサート:なぜ重要なのか
小規模なデータセット(10〜20行)では、1行ずつ挿入するかバッチで挿入するかはあまり重要ではありません。大規模なデータセットでは、その違いは重大です。
単一行のINSERTは次のようになります:
INSERT INTO `users` (`id`, `name`, `email`) VALUES (1, 'Alice', 'alice@example.com');
INSERT INTO `users` (`id`, `name`, `email`) VALUES (2, 'Ben', 'ben@example.com');
-- ... さらに198のステートメント
バッチINSERTは次のようになります:
INSERT INTO `users` (`id`, `name`, `email`) VALUES
(1, 'Alice', 'alice@example.com'),
(2, 'Ben', 'ben@example.com'),
(3, 'Sara', 'sara@example.com'),
-- ... 最大100行
;
INSERT INTO `users` (`id`, `name`, `email`) VALUES
(101, 'David', 'david@example.com'),
-- ... 次のバッチ
;
バッチバージョンが高速な理由は2つあります。第一に、行ごとではなくバッチごとに1つのネットワークラウンドトリップがあります。第二に、データベースは多くの小さな書き込みよりも1つの大きな書き込みをより最適化できます — トランザクションのオーバーヘッド、インデックスの更新、先行書き込みロギングはすべて行ごとではなくステートメントごとに1回発生します。
コンバーターが一般的にすべてを1つのステートメントに入れるのではなく100〜500行でバッチをキャップする理由は、メモリと信頼性です。10,000行の単一INSERTステートメントはメモリ制限に達したり、解析が遅くなったり、途中で失敗した場合にどの行が問題だったかについて有用なフィードバックが得られません。小さなバッチのほうがより実用的です。
ツールができないこと
制限を明確にしておくと、後のデバッグセッションを節約できます。
外部キーなし。 コンバーターはテーブル間の関係について何も知りません。JSONに別のテーブルを参照するuser_idフィールドがある場合、プレーンなINTカラムが得られます — FOREIGN KEY制約もREFERENCES句もありません。これらは手動で追加する必要があります。
インデックスなし。 生成されたCREATE TABLEにはインデックスの定義がありません。大規模なデータセットをインポートしてからクエリを実行する場合は、フィルタリングや結合に使用するカラムにINDEXやUNIQUE INDEXの定義を追加する必要があります。これは常にインポート後のステップです。
主キーのマーキングなし。 ツールはid INTカラムを作成するかもしれませんが、PRIMARY KEYを追加したりAUTO_INCREMENTを付けたりしません。idを適切な自動インクリメントの主キーにしたい場合は、MySQLでそのカラム定義にPRIMARY KEY AUTO_INCREMENTを追加するか、PostgreSQLでSERIALに切り替えてください。
複雑またはネストされた型なし。 フィールドの値がオブジェクト("address": {"street": "123 Main St", "city": "Portland"})または配列("tags": ["developer", "remote"])の場合、ツールはそれをリレーショナルスキーマにフラット化できません。JSON文字列としてシリアライズするかフィールドをスキップするかのどちらかになります。決断が必要です:JSONカラムとして保存する(MySQL 5.7+とPostgreSQLはネイティブのJSONカラムをサポート)、別のカラムにフラット化する、または別のテーブルに正規化する。
アップサートロジックなし。 生成されたステートメントは競合処理のないプレーンなINSERTです。既にデータが存在する、重複する主キーを持つテーブルに対して実行すると、制約違反エラーが発生します。冪等なインポートのためには、MySQLのINSERT IGNORE、PostgreSQLのON CONFLICT DO NOTHING、またはSQL ServerのMERGEが必要ですが、ツールはこれらを自動的に追加しません。
トランザクションなし。 出力はBEGIN TRANSACTION / COMMITでラップされていません。大規模なインポートでは、全体をトランザクションでラップするのが良い実践です — 途中で何かが失敗した場合、きれいにロールバックできます。生成されたステートメントの周りに自分で追加してください。
このツールを使うべき場合とそうでない場合
JSON to SQLコンバーターは特定の状況に適したツールです。すべてのことに適したツールではありません。
使うべき場合:
- 外部ソースからの一度限りのデータインポートがある
- データベース間でデータを移行してクイックなSQLダンプが必要
- 現実的に見えるデータでデモやテストデータベースをセットアップしている
- アプリケーションスタックなしでSQLを直接扱っている
- SQLアクセスしか持っていない人とデータを共有する必要がある
ORMやETLツールを使うべき場合:
- 繰り返し可能なバージョン管理されたシードデータが必要 — PrismaのDb seed、Djangoのfixtures、またはRailsのシードファイルのようなツールは移行ワークフローと統合されており、安全に再実行できる
- インポートが複雑で、単なる挿入だけでなくデータの変換を伴う
- プログラム的に関係と外部キー制約を処理する必要がある
- これをスケジュール通りに定期的に行っている — 適切なETLスクリプトを書くか、Apache NiFi、dbt、またはpandasとsqlalchemyを使ったシンプルなPythonスクリプトのようなツールを使う
JSON to SQLコンバーターは本質的に「このデータをテーブルに入れるだけ」というシナリオのためのショートカットです。シナリオがそれより複雑な場合は、より複雑なツールが必要です。
実践的なワークフロー
うまく機能するシーケンスは次の通りです:
-
最初にJSONをフォーマットする。 エクスポートが圧縮されている場合は、まずJSONフォーマッターに貼り付けてください。これにより構造を確認し、変換前に問題をキャッチできます。
-
配列の構造を確認する。 コンバーターはすべてのオブジェクトが同じトップレベルキーを持つJSONオブジェクトの配列を想定しています。エクスポートが配列ではなくルートオブジェクト(
{"data": [...]})にラップされている場合は、最初に配列を抽出してください。 -
ダイアレクトを選択してJSONを貼り付けます。生成されたSQLをコピーします。
-
CREATE TABLEを確認する。 何かを実行する前に、カラム型を読んで間違って見えるものを修正してください — 特に日付フィールド、大きなテキストフィールド、実際には文字列であるべき数値フィールド。
-
必要な制約を追加する。 主キーをマークし、適切な場所に
NOT NULLを追加し、メールカラムにUNIQUEを追加し、必要なFOREIGN KEY制約を追加する。 -
最初にCREATE TABLEを実行し、次にINSERTを実行します。テーブルが既に存在して再インポートする場合は、最初に
DROP TABLE IF EXISTSするか、何らかの競合解決戦略を使用するかを決定してください。 -
行数を確認する。 インポート後、
SELECT COUNT(*) FROM your_table;を実行し、JSONの行数と比較します。一致すれば完了です。
出力SQLのフォーマット
生成されたSQLは通常読みやすいですが、常に自分の基準に合っているわけではありません。INSERTステートメントを一貫してフォーマットしたい場合(揃ったカラム、キーワードの一貫した大文字小文字)は、出力をSQLフォーマッターに通してください。意味論は変わりませんが、SQLを確認してdiffを取るのが容易になります。
まとめ
大規模なJSONデータセットのINSERTステートメントを手動で書くことは、自動化が最も意味をなすタスクの一つです。JSON to SQLコンバーターは機械的な変換を処理します:配列を読み、型を推測し、スキーマを生成し、ターゲットダイアレクトでバッチINSERTステートメントを作成します。
まだ注意が必要な部分は、スキーマの確認(型、制約、インデックス)、ネストされたデータの処理、そして再実行が必要な場合のINSERTロジックの冪等化です。ツールは純粋に機械的な90%を担当します。残りの10%は、どの自動化も代替できないデータベース設計の判断です。
JSONデータを定期的に扱うなら、このツールとブックマークすべき他の2つのツールがあります:入力を確認してクリーンアップするためのJSONフォーマッターと、マイグレーションファイルにコミットする前に生成した出力を整理するためのSQLフォーマッターです。