mygems

데이터베이스 없이 CSV 파일에 SQL 쿼리 실행하는 방법 (2026 guide)

GROUP BY에 Postgres는 필요 없습니다. DuckDB, SQLite, q, 휴대폰으로 CSV를 바로 조회하는 방법과 상황별 선택법입니다.

작성: uos ·

결론부터 말씀드리면, DuckDB로 파일을 바로 가리키면 됩니다.

duckdb -c "SELECT region, SUM(amount) FROM 'sales.csv' GROUP BY region"

스키마도, 가져오기(import)도, 서버도, CREATE TABLE도 필요 없습니다. CSV는 있던 자리에 그대로 있고, 헤더 행이 그대로 열 이름이 됩니다. 오늘 한 가지만 설치한다면 이것을 설치하세요.

이 글의 나머지는 이것이 왜 되는지, DuckDB를 쓸 수 없을 때는 어떻게 하는지, 그리고 누구나 걸려 넘어지는 두 가지, 즉 파일 이름의 따옴표와 이름에 공백이 들어간 열을 다루는 방법을 설명합니다.

파일을 어딘가에 불러올 필요가 없는 이유

「먼저 데이터를 데이터베이스에 넣어야 한다」는 생각은 SQL 엔진이 자기 저장소만 읽을 수 있던 시절의 습관입니다. 요즘 엔진에는 그런 제약이 없습니다. DuckDB, SQLite, 그리고 몇몇 작은 CLI 도구는 쿼리 하나가 도는 동안만 CSV를 테이블처럼 다루고, 샘플링으로 타입을 추정해 답을 내고, 모두 잊어버립니다.

이러면 계산이 완전히 달라집니다. 메일로 받은 파일에 대한 일회성 질문은 스키마와 로드 스크립트와 정리 작업을 들일 가치가 없습니다. 한 줄이면 충분합니다.

진짜 데이터베이스에 적재하는 것이 맞는 경우는 딱 두 가지입니다. 여러 사람이 데이터를 반복해서 조회하는 경우, 그리고 평평한 파일 하나로는 표현할 수 없는 제약 조건과 관계가 필요한 경우입니다. 「이 내보내기 파일에서 지역별 합계가 얼마인가」는 둘 다 아닙니다.

선택지를 솔직하게 비교

도구설치잘 맞는 용도약한 점
DuckDB바이너리 하나, 의존성 없음수십 GB까지 거의 전부이 작업에서는 거의 없음
SQLite + .importmacOS와 Linux에 기본 포함나중을 위해 테이블 보관타입. 전부 TEXT로 들어옴
qpip install q-text-as-data셸 파이프라인의 간단한 한 줄 명령큰 파일. 더 느림
csvsql (csvkit)pip install csvkit실제 DB용 CREATE TABLE 생성속도. 여기서 가장 느림
Pandas + pandasql이미 pandas가 있을 때결과를 파이썬 코드로 이어갈 때SQL 방언이 Postgres가 아니라 SQLite
SQL이 되는 휴대폰 앱App Store / Play파일이 휴대폰 메일로 왔을 때스크립트로 자동화하고 싶은 모든 일

솔직한 순위는 짧습니다. 거의 모든 경우에 DuckDB, 아무것도 설치할 수 없으면 SQLite, 컴퓨터 앞이 아니라면 휴대폰 앱입니다. 나머지도 괜찮고 쓸 일이 있지만, 이 작업에서 DuckDB를 넘어서는 것은 없습니다.

DuckDB 제대로 쓰기

설치는 바이너리 하나입니다. brew install duckdb를 쓰거나, 내려받아 경로(path)에 두세요. 그다음에는 이렇게 합니다.

duckdb
D SELECT * FROM 'orders.csv' LIMIT 10;

바로 알아 둘 것이 두 가지 있습니다.

파일 이름에는 작은따옴표가 필요합니다. FROM orders.csv는 스키마 orders와 테이블 csv로 해석되고, 오류 메시지만 봐서는 원인을 알기 어렵습니다. 올바른 형태는 FROM 'orders.csv'입니다.

공백이 있는 열 이름에는 큰따옴표가 필요합니다. SELECT "Order Date" FROM …처럼 씁니다. 여기에 작은따옴표를 쓰면 오류 없이 모든 행에 그 문자열이 그대로 나와서, 쿼리가 성공한 것처럼 보입니다.

디렉터리 전체를 glob으로 읽을 수 있습니다. 내보내기가 월별 조각으로 왔을 때 유용합니다.

SELECT * FROM 'exports/*.csv'

타입 추정이 틀릴 때는 대개 앞에 0이 붙은 ID 열이나 인식하지 못하는 형식의 날짜 때문입니다. 추정과 싸우지 말고 읽기 방식을 직접 지정하세요.

SELECT * FROM read_csv('orders.csv',
  header = true,
  columns = {'id': 'VARCHAR', 'ordered_at': 'DATE', 'amount': 'DOUBLE'},
  dateformat = '%d/%m/%Y'
)

read_csv는 delim, quote, escape, ignore_errors = true, sample_size = -1도 받습니다. sample_size = -1은 처음 20,480행이 아니라 파일 전체를 훑은 뒤 타입을 정합니다. 열이 만 행 동안 숫자처럼 보이다가 N/A를 만나는 경우의 해결책이 바로 이것입니다.

결과를 파일로 다시 저장하는 것도 SQL 안에서 할 수 있습니다.

COPY (SELECT * FROM 'orders.csv' WHERE amount > 1000)
TO 'large-orders.csv' (HEADER, DELIMITER ',');

같은 파일을 여러 번 이상 조회할 예정이라면 COPY (SELECT * FROM 'orders.csv') TO 'orders.parquet'로 한 번 변환하고 이후에는 Parquet을 조회하세요. 타입이 있고, 압축되어 있으며, 보통 스캔이 5배에서 20배 빠릅니다.

SQLite: 아무것도 설치할 수 없을 때

sqlite3는 macOS와 대부분의 Linux 배포판에 들어 있어서, 제한이 걸린 컴퓨터에서도 쓸 수 있는 선택지입니다.

sqlite3 shop.db
sqlite> .mode csv
sqlite> .import orders.csv orders
sqlite> SELECT region, COUNT(*) FROM orders GROUP BY region;

주의할 점이 있고, 중요합니다. .import는 모든 열을 TEXT로 만듭니다. 그러면 정렬이 사전순이 되어 100이 20보다 앞에 오고, SUM()도 예상 밖의 결과를 냅니다. 쿼리할 때 캐스팅(CAST(amount AS REAL))하거나, 가져오기 전에 실제 타입으로 테이블을 만드세요.

DuckDB와 달리 shop.db가 남습니다. 내일 그 테이블을 다시 쓸 생각이라면 의도한 결과이고, 아니라면 불필요한 파일입니다.

q: 셸 파이프라인용

쿼리가 셸 명령 체인의 한 단계일 때, q는 다른 Unix 도구처럼 표준 입력을 읽습니다.

q -H -d, "SELECT region, SUM(amount) FROM - GROUP BY region" < orders.csv

-H는 첫 행이 헤더라는 뜻이고, -d,는 구분자를 정하며, -는 stdin입니다. 내부는 SQLite라서 방언도 SQLite입니다. 큰 파일에서는 현실적인 선택지 중 가장 느립니다. 수십만 행까지는 괜찮지만 그 이상은 힘듭니다.

파일이 휴대폰에 있을 때

내보내기 파일이 메일 첨부로 와 있고 책상 앞이 아니라면 위의 방법은 아무 도움이 되지 않습니다. 자주 있는 상황인데, 흔한 해결책인 「나에게 보내 놓고 나중에 보기」는 좋은 방법이 아닙니다.

CSV File Viewer - Smart CSViPhone 및 iPad · Android

iOS와 Android에서 파일에 대해 진짜 SQL을 실행합니다. 휴대폰 키보드로 쿼리를 입력하고 싶지 않을 때를 위한 시각적 필터 빌더가 있고, 아직 열 이름을 모를 때를 위한 일상 언어 도우미도 있습니다.

기능

방언이 SQLite이므로 이 글의 쿼리는 그대로 옮겨 쓸 수 있지만, 늘 있는 주의점이 있습니다. DuckDB 예제의 윈도 함수는 쓸 수 없고, CAST 습관은 똑같이 필요합니다.

준비해 두면 좋은 쿼리

이 파일에 무엇이 들어 있는지:

SELECT * FROM 'file.csv' LIMIT 20;
DESCRIBE SELECT * FROM 'file.csv';
SELECT COUNT(*) FROM 'file.csv';

NULL이 어디에 있는지. 열을 하나씩 확인하는 것은 번거로우니 이렇게 합니다.

SELECT COUNT(*) - COUNT(email) AS missing_email,
       COUNT(*) - COUNT(phone) AS missing_phone
FROM 'contacts.csv';

키 중복:

SELECT id, COUNT(*) AS n
FROM 'orders.csv'
GROUP BY id HAVING n > 1
ORDER BY n DESC;

내보내기 파일 두 개 조인은 이 방식의 가치를 가장 자주 보여 주는 쿼리입니다. 시트 두 개를 VLOOKUP으로 연결하는 것보다 훨씬 낫기 때문입니다.

SELECT o.id, o.amount, c.name
FROM 'orders.csv' o
JOIN 'customers.csv' c ON c.id = o.customer_id;

날짜 열에서 월별 합계:

SELECT date_trunc('month', ordered_at) AS month, SUM(amount)
FROM 'orders.csv'
GROUP BY month ORDER BY month;

잘 안 될 때

전부 한 열로 나왔다. 구분자가 쉼표가 아닙니다. 유럽의 많은 지역에서는 세미콜론이 표준입니다. read_csv('f.csv', delim = ';')를 쓰세요. 그보다 더 이상한 파일이라면 단순히 특이한 것이 아니라 실제로 깨진 것일 수 있습니다.

숫자 열이 합산되지 않는다. 숫자가 아닌 값이 섞여 있어서 텍스트로 추정된 것입니다. SELECT amount FROM 'f.csv' WHERE TRY_CAST(amount AS DOUBLE) IS NULL LIMIT 10;으로 원인을 찾을 수 있습니다. 대개 통화 기호, 천 단위 구분자, 또는 N/A 문자열입니다.

앞의 0이 사라졌다. 열이 숫자로 읽힌 것입니다. columns에서 VARCHAR로 선언하고, 이후에는 스프레드시트로 열지 마세요.

헤더 행이 파일 곳곳에 반복된다. 여러 내보내기 파일이 이어 붙여진 것입니다. WHERE id <> 'id'는 거친 해결책이지만 대개 맞는 방법입니다.

메모리가 부족했다. DuckDB는 스트리밍으로 처리하므로, 보통은 파일 전체에 대한 ORDER BY나 DISTINCT가 원인입니다. 임시 디렉터리(SET temp_directory = '/tmp/duckdb')를 지정하면 디스크로 넘겨서 처리합니다. 규모별로 무엇이 감당되는지 더 넓게 알고 싶다면 1GB CSV 파일 여는 방법을 참고하세요.

자주 묻는 질문

CSV 파일을 가져오지 않고 SQL을 실행할 수 있나요?
네. DuckDB는 `SELECT * FROM 'file.csv'`로 CSV를 그 자리에서 조회하며, 열 이름은 헤더 행에서, 타입은 샘플링으로 추정합니다. 가져오기도 테이블 생성도 없고 파일은 그대로입니다. SQLite도 가능하지만 .import 단계에서 영구 테이블이 만들어집니다.
큰 CSV를 SQL로 가장 빠르게 조회하는 방법은 무엇인가요?
설정이 필요 없는 선택지 중에서는 DuckDB가 압도적으로 빠릅니다. 파일을 병렬로 읽고 전부 메모리에 올리지 않고 스트리밍하므로 RAM보다 훨씬 큰 파일도 다룰 수 있습니다. 같은 파일을 반복해서 조회한다면 COPY … TO 'file.parquet'로 한 번 Parquet으로 변환하세요. 스캔이 5배에서 20배 빨라집니다.
이름에 공백이 있는 CSV 열은 어떻게 조회하나요?
큰따옴표로 감싸세요. SELECT "Order Date" FROM 'orders.csv'처럼 씁니다. 작은따옴표를 쓰면 문자열 리터럴이 되어 오류 없이 모든 행에 같은 문자열이 나오므로, 쿼리가 성공한 것처럼 보입니다.
휴대폰에서 CSV 파일에 SQL 쿼리를 실행할 수 있나요?
네. Smart CSV Viewer는 iOS와 Android에서 CSV에 SQL을 실행할 수 있고, 휴대폰 키보드로 쿼리를 입력하는 대신 시각적 필터 빌더도 쓸 수 있습니다. 엔진은 SQLite이므로 방언은 DuckDB가 아니라 SQLite의 것입니다.
숫자 열이 합산되지 않는 이유는 무엇인가요?
통화 기호, 천 단위 구분자, N/A 같은 자리표시자 등 숫자가 아닌 값이 열 어딘가에 있어서 텍스트로 추정된 것입니다. TRY_CAST(col AS DOUBLE) IS NULL로 문제의 행을 찾아 정리하거나, read_csv의 `columns` 인수로 열을 명시해서 읽으세요.
언제 CSV를 진짜 데이터베이스에 적재해야 하나요?
두 가지 경우입니다. 여러 사람이 데이터를 반복해서 조회하는 경우, 그리고 평평한 파일 하나로는 표현할 수 없는 제약 조건과 관계가 필요한 경우입니다. 메일로 받은 파일에 대한 일회성 질문에는 스키마도 로드 스크립트도 필요 없습니다.

uos — 휴대폰에 SQL 엔진을 올린 Smart CSV Viewer를 만들고 있어서, 무엇을 데이터베이스에 넣어야 하는지 자주 고민하게 됩니다.