CSV to SQL 변환기
CSV 데이터에서 SQL INSERT 문 생성
CSV 입력
SQL 출력
CSV to SQL 변환이란?
CSV to SQL 변환은 쉼표로 구분된 값을 관계형 데이터베이스가 실행할 수 있는 SQL 문으로 변환합니다. 가장 일반적인 출력은 한 쌍으로 구성됩니다. 열을 정의하는 CREATE TABLE 문과, CSV 파일의 행으로 해당 열을 채우는 하나 이상의 INSERT INTO 문입니다.
CSV 파일은 RFC 4180 명세를 따릅니다. 각 줄은 하나의 레코드이며, 필드는 구분자(일반적으로 쉼표)로 분리되고, 구분자나 줄바꿈을 포함하는 필드는 큰따옴표로 감쌉니다. SQL은 PostgreSQL, MySQL, SQLite, SQL Server 같은 시스템에서 데이터를 관리하는 표준 언어입니다. 이 두 형식 간 변환은 소프트웨어 개발에서 가장 빈번한 데이터 가져오기 작업 중 하나입니다.
올바른 CSV-to-SQL 변환기는 인용을 처리하고, 값 내의 작은따옴표를 이스케이프하며(SQL 표준에 따라 ''로 두 배 처리), 열 헤더를 유효한 SQL 식별자에 매핑하고, 선택적으로 데이터 타입을 추론할 수 있습니다. 기본 이스케이프 처리 외에도 좋은 변환기는 헤더 이름을 정규화하고(공백과 하이픈을 밑줄로 교체), 출력을 트랜잭션 블록으로 감싸서 가져오기 실패 시 부분 데이터가 테이블에 남지 않고 깔끔하게 롤백되도록 합니다. 변환기 없이 수백 행의 INSERT 문을 직접 작성하는 것은 오류가 발생하기 쉽고 느립니다.
CSV to SQL 변환기를 사용하는 이유
스프레드시트 데이터에서 SQL INSERT 문을 직접 작성하는 것은 번거롭고 구문 오류를 유발합니다. 변환기는 반복적인 부분을 자동화하여 스키마 설계와 데이터 유효성 검사에 집중할 수 있게 해줍니다.
CSV to 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: 가져오기 방법 선택
소규모에서 중규모 데이터셋(10,000행 미만)의 경우 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;"