CSVからSQL変換
CSVデータからSQL INSERT文を生成
CSV入力
SQL出力
CSVからSQLへの変換とは?
CSVからSQLへの変換は、カンマ区切りの値をリレーショナルデータベースが実行できる構造化クエリ言語(SQL)文に変換する処理です。最も一般的な出力は、カラムを定義するCREATE TABLE文と、CSVファイルの各行のデータをそれらのカラムに挿入する1つ以上のINSERT INTO文のペアです。
CSVファイルはRFC 4180仕様に従います。各行はレコードであり、フィールドは区切り文字(通常はカンマ)で分けられ、区切り文字や改行を含むフィールドはダブルクォートで囲まれます。一方SQLは、PostgreSQL、MySQL、SQLite、SQL Serverといったシステムでデータを管理するための標準言語です。この2つの形式間の変換は、ソフトウェア開発における最も頻繁なデータインポート作業のひとつです。
適切なCSV-to-SQLコンバーターはクォート処理を行い、値内のシングルクォートをエスケープし(SQLの標準に従って''に変換)、カラムヘッダーを有効なSQL識別子にマッピングし、オプションでデータ型を推論します。基本的なエスケープに加え、優れたコンバーターはヘッダー名を正規化し(スペースやハイフンをアンダースコアに置換)、出力をトランザクションブロックで囲むことで、インポートが失敗した際にテーブルに不完全なデータを残さずにロールバックできるようにします。コンバーターなしでは、数百行でもINSERT文を手書きすることはエラーが起きやすく非効率です。
CSVからSQLへの変換ツールを使う理由
スプレッドシートのデータからSQL INSERT文を手書きするのは手間がかかり、構文エラーが発生しやすいです。コンバーターは繰り返し作業を自動化するので、スキーマ設計とデータ検証に集中できます。
CSVからSQL変換のユースケース
SQLデータ型リファレンス
CSVをSQLに変換する際、CSVには型のメタデータがないため、すべてのカラムはデフォルトでTEXTになります。カラムの型がわかっている場合は、CREATE TABLE出力のTEXTを置き換えることができます。この表は最も一般的なSQLの型とその使用場面を示しています。
| 型 | 用途 | 備考 |
|---|---|---|
| TEXT / VARCHAR | Strings, mixed content | Default safe choice; works in every SQL dialect |
| INTEGER / INT | Whole numbers (age, count, ID) | Use when column contains only digits with no decimals |
| REAL / FLOAT | Decimal numbers (price, rate) | Needed for columns with dots like 19.99 or 3.14 |
| DATE | ISO 8601 dates (2024-01-15) | Requires consistent formatting; varies by database |
| BOOLEAN | true/false, 1/0, yes/no | MySQL uses TINYINT(1); PostgreSQL has native BOOL |
| NUMERIC / DECIMAL | Exact precision (currency) | Specify scale: DECIMAL(10,2) for money columns |
| BLOB / BYTEA | Binary data | Rarely needed in CSV imports; use for hex-encoded fields |
SQLダイアレクト比較
SQL構文はデータベースエンジンによって異なります。生成されるINSERT文はほとんどのシステムで動作する標準SQLを使用していますが、特定の機能は異なります。この表はCSVデータをインポートする際に最も重要な違いをまとめています。
| 機能 | PostgreSQL | MySQL | SQLite | SQL Server |
|---|---|---|---|---|
| Identifier quoting | "col" | `col` | "col" | [col] |
| String escape | '' | '' or \' | '' | '' |
| INSERT syntax | INSERT INTO | INSERT INTO | INSERT INTO | INSERT INTO |
| Batch INSERT | VALUES (),()… | VALUES (),()… | VALUES (),()… | max 1000 rows |
| Auto-increment | SERIAL | AUTO_INCREMENT | AUTOINCREMENT | IDENTITY(1,1) |
| Upsert / merge | ON CONFLICT | ON DUPLICATE KEY | ON CONFLICT | MERGE |
| NULL handling | IS NULL | IS NULL / <=> | IS NULL | IS NULL |
| COPY from CSV | COPY … FROM | LOAD DATA INFILE | .import | BULK INSERT |
INSERT vs COPY:インポート方式の選択
小〜中規模のデータセット(1万行未満)では、INSERT文がうまく機能し、あらゆるSQLデータベース間で移植可能です。大規模なインポートでは、SQLパーサーを完全にバイパスするバルクロードコマンドがデータベースから提供されています。
コード例
これらの例は、異なるプログラミング言語でCSVをSQL INSERT文に変換する方法を示しています。それぞれシングルクォートのエスケープとカラム名の変換を処理しています。
// CSV → SQL INSERT statements
const csv = `name,age,city
Alice,30,Berlin
Bob,25,Tokyo`
function csvToSql(csv, table = 'data') {
const rows = csv.trim().split('\n').map(r => r.split(','))
const [headers, ...data] = rows
const cols = headers.map(h => h.trim().toLowerCase().replace(/\s+/g, '_'))
const values = data.map(row =>
' (' + row.map(v => `'${v.replace(/'/g, "''").trim()}'`).join(', ') + ')'
)
return `INSERT INTO ${table} (${cols.join(', ')}) VALUES
${values.join(',\n')};`
}
console.log(csvToSql(csv, 'users'))
// → INSERT INTO users (name, age, city) VALUES
// ('Alice', '30', 'Berlin'),
// ('Bob', '25', 'Tokyo');import csv
import io
csv_string = """name,age,city
Alice,30,Berlin
Bob,25,Tokyo"""
reader = csv.reader(io.StringIO(csv_string))
headers = [h.strip().lower().replace(' ', '_') for h in next(reader)]
table = 'users'
rows = list(reader)
# CREATE TABLE
col_defs = ', '.join(f'{h} TEXT' for h in headers)
print(f'CREATE TABLE {table} ({col_defs});')
# → CREATE TABLE users (name TEXT, age TEXT, city TEXT);
# INSERT statements
for row in rows:
vals = ', '.join(f"'{v.replace(chr(39), chr(39)*2)}'" for v in row)
print(f"INSERT INTO {table} ({', '.join(headers)}) VALUES ({vals});")
# → INSERT INTO users (name, age, city) VALUES ('Alice', '30', 'Berlin');
# → INSERT INTO users (name, age, city) VALUES ('Bob', '25', 'Tokyo');package main
import (
"encoding/csv"
"fmt"
"strings"
)
func csvToSQL(data, table string) string {
r := csv.NewReader(strings.NewReader(data))
records, _ := r.ReadAll()
if len(records) < 2 {
return ""
}
headers := records[0]
var vals []string
for _, row := range records[1:] {
escaped := make([]string, len(row))
for i, v := range row {
escaped[i] = "'" + strings.ReplaceAll(v, "'", "''") + "'"
}
vals = append(vals, " ("+strings.Join(escaped, ", ")+")")
}
return fmt.Sprintf("INSERT INTO %s (%s) VALUES\n%s;",
table, strings.Join(headers, ", "), strings.Join(vals, ",\n"))
}
func main() {
csv := "name,age,city\nAlice,30,Berlin\nBob,25,Tokyo"
fmt.Println(csvToSQL(csv, "users"))
// → INSERT INTO users (name, age, city) VALUES
// ('Alice', '30', 'Berlin'),
// ('Bob', '25', 'Tokyo');
}# SQLite: import CSV directly into a table sqlite3 mydb.db <<EOF .mode csv .import data.csv users SELECT * FROM users LIMIT 5; EOF # PostgreSQL: COPY from CSV file (server-side) psql -c "COPY users FROM '/path/to/data.csv' WITH (FORMAT csv, HEADER true);" # PostgreSQL: \copy from CSV (client-side, no superuser needed) psql -c "\copy users FROM 'data.csv' WITH (FORMAT csv, HEADER true);" # MySQL: LOAD DATA from CSV mysql -e "LOAD DATA INFILE '/path/to/data.csv' INTO TABLE users FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 ROWS;"