Nhập CSV là việc của CLI, không phải của SQL
Phương ngữ SQL của SQLite không có câu lệnh IMPORT. Việc nạp CSV là tính năng của shell dòng lệnh sqlite3, qua một lệnh chấm (dot-command) tên là .import. Đây là thay đổi tư duy quan trọng nếu bạn quen với LOAD DATA INFILE của MySQL hay COPY của Postgres: những lệnh đó chạy trên máy chủ, còn .import là việc công cụ phía máy khách làm giúp bạn, đọc tệp và chạy các câu lệnh INSERT ở bên dưới.
Vì vậy mọi thứ trong trang này đều giả định bạn đang ở trong shell sqlite3:
sqlite3 mydata.db
Nếu bạn cần nhập dữ liệu từ code ứng dụng (Python, Node, Go), bạn sẽ đọc CSV bằng ngôn ngữ đó và dùng các câu lệnh INSERT có tham số. Ta sẽ tìm hiểu cách đó trong chương tích hợp với ứng dụng. Ở đây, ta tập trung vào CLI.
.import cơ bản
Cách ngắn nhất: báo cho SQLite biết tệp là CSV, rồi trỏ .import vào tệp và tên bảng.
.mode csv
.import people.csv people
Có hai trường hợp tùy vào việc people đã tồn tại hay chưa:
- Bảng chưa tồn tại: SQLite tạo bảng, dùng dòng đầu của CSV làm tên cột. Mọi cột đều có affinity
TEXT. - Bảng đã tồn tại: SQLite thêm mọi dòng của tệp vào như dữ liệu. Dòng tiêu đề, nếu có, cũng thành một hàng.
Trường hợp thứ hai là chỗ hầu hết mọi người vấp ngay lần đầu. Nếu CSV có dòng tiêu đề và bảng đã tồn tại, bạn phải chủ động bỏ qua nó.
Bỏ qua dòng tiêu đề khi bảng đã có
Dùng --skip 1 để bảo .import bỏ qua N dòng đầu:
CREATE TABLE people (
name TEXT,
age INTEGER,
city TEXT
);
.import --csv --skip 1 people.csv people
--csv là cách viết tắt của .mode csv chỉ áp dụng cho riêng lệnh này, nên bạn không phải đặt chế độ riêng. --skip 1 bỏ dòng tiêu đề. Các dòng còn lại được thêm vào people theo thứ tự cột.
Kiểm tra nhanh sau khi nhập:
SELECT count(*) FROM people;
SELECT * FROM people LIMIT 5;
Thứ tự cột trong tệp phải khớp với thứ tự cột trong bảng. Không có cơ chế ánh xạ theo tiêu đề: .import chỉ ghép trường thứ N với cột thứ N.
Để SQLite tự tạo bảng
Khi khám phá dữ liệu, cách dễ nhất là bỏ qua hẳn CREATE TABLE và để .import dựng bảng từ dòng tiêu đề:
.mode csv
.import sales.csv sales
.schema sales
.schema sales sẽ hiện ra kiểu như:
CREATE TABLE sales(
"order_id" TEXT,
"amount" TEXT,
"ordered_at" TEXT
);
Để ý rằng mọi cột đều là TEXT. Đó là có chủ ý: .import không cố đoán kiểu dữ liệu. Nếu bạn muốn amount là số thực và ordered_at là mốc thời gian đúng nghĩa, hãy tự tạo bảng trước với kiểu phù hợp, rồi nhập với --skip 1. Type affinity của SQLite sẽ chuyển các chuỗi số thành số nguyên và số thực khi thêm dữ liệu.
Ký tự phân tách tùy chỉnh: TSV, gạch đứng, chấm phẩy
.mode csv dùng dấu phẩy. Với tệp phân tách bằng tab, hãy đổi chế độ:
.mode tabs
.import data.tsv events
Với các ký tự phân tách khác, dùng .separator sau khi chọn chế độ:
.mode csv
.separator "|"
.import pipe_data.txt events
Một điều cần biết: .mode csv tuân theo quy tắc đặt dấu ngoặc kép của RFC 4180, nên các trường chứa dấu phẩy hay ký tự xuống dòng vẫn chạy đúng miễn là được bọc đúng cách bằng ". .mode tabs là chế độ đơn giản hơn, chỉ tách theo ký tự và không xử lý dấu ngoặc kép. Nếu tệp có trường được bọc ngoặc kép chứa ký tự phân tách, hãy giữ .mode csv và đổi ký tự phân tách.
Ví dụ thực tế từ đầu đến cuối
Giả sử orders.csv trông như sau:
order_id,customer,amount,ordered_at
1001,Ada,49.99,2026-01-12
1002,Boris,12.50,2026-01-13
1003,"Chen, Wei",199.00,2026-01-14
Lưu ý dòng 3 có dấu phẩy bên trong một trường được bọc ngoặc kép. Đây là toàn bộ phiên làm việc:
Trong shell thật, khối INSERT sẽ được thay bằng một lệnh duy nhất .import --csv --skip 1 orders.csv orders. Trường "Chen, Wei" được giữ nguyên vì chế độ CSV tôn trọng dấu ngoặc kép. Nhờ kiểu của các cột, amount trở thành số thực và order_id thành số nguyên.
Đặt lệnh nhập trong một transaction
.import chạy một lệnh INSERT cho mỗi dòng. Với vài nghìn dòng thì không sao. Với một triệu dòng, nó sẽ chậm khủng khiếp, trừ khi bạn đặt toàn bộ trong một transaction để SQLite không phải commit sau mỗi dòng:
BEGIN;
.import --csv --skip 1 big_file.csv events
COMMIT;
Chỉ một thay đổi này có thể biến một lần nhập mất vài phút thành vài giây. Nếu có gì thất bại giữa chừng, ROLLBACK hủy phần dữ liệu đã nạp dở, rất tiện khi cần thử lại.
Bạn còn có thể tăng tốc thêm bằng cách xóa index trước khi nhập rồi tạo lại sau đó: chi phí cập nhật index cho từng dòng cộng dồn lại khá lớn.
Những lỗi thường gặp và cách sửa
Error: expected N columns but found M: số trường của một dòng không khớp với bảng. Thường là do:
- Một dấu phẩy lạc trong trường không được bọc ngoặc kép. Xuất lại tệp với dấu ngoặc kép CSV đúng chuẩn, hoặc chuyển sang
.mode csv(RFC 4180) thay vì.mode tabs. - Một dòng trống ở cuối tệp. Sửa tệp hoặc dùng
--skipmột cách khéo léo. - Bảng có nhiều cột hơn CSV. Hoặc thêm các cột còn thiếu vào tệp, hoặc nhập vào một bảng trung gian có cấu trúc phù hợp rồi chép sang bảng thật.
Dòng tiêu đề xuất hiện như dữ liệu: bạn quên --skip 1 khi bảng đã tồn tại. Xóa hàng đó (DELETE FROM t WHERE rowid = 1) và chạy lại với cờ này.
Số bị lưu thành chuỗi: bạn để .import tự tạo bảng, nên mọi cột đều là TEXT. Xóa bảng, định nghĩa lại rõ ràng với các cột INTEGER/REAL, rồi nhập lại.
Error: no such file: đường dẫn được tính từ thư mục nơi bạn mở sqlite3, không phải từ tệp cơ sở dữ liệu. Dùng đường dẫn tuyệt đối hoặc cd đến đúng thư mục trước khi mở shell.
CLI in ra số dòng khi có lỗi, đó là cách nhanh nhất để tìm dòng gây lỗi trong một tệp lớn.
Tóm tắt nhanh
.importlà lệnh chấm của CLI, không phải SQL. Chạy nó bên trong shellsqlite3.- Dùng
--csvđể xử lý dấu ngoặc kép đúng cách,--skip 1để bỏ qua dòng tiêu đề. - Nếu bảng chưa tồn tại,
.importtạo bảng từ dòng tiêu đề, nhưng mọi cột sẽ làTEXT. Tự tạo bảng để có kiểu dữ liệu đúng. - Đặt các lần nhập lớn trong
BEGIN/COMMITđể tránh mỗi dòng một transaction. - Thứ tự cột trong tệp phải khớp với thứ tự cột trong bảng.
Tiếp theo: xuất dữ liệu ra ngoài
Nhập dữ liệu mới là một nửa câu chuyện. Cùng shell đó có thể xuất kết quả truy vấn hay cả bảng ra CSV, JSON hoặc SQL, hữu ích cho sao lưu, pipeline dữ liệu và chuyển dữ liệu sang công cụ khác. Đó là nội dung của phần xuất dữ liệu.
Câu hỏi thường gặp
Làm sao nhập tệp CSV vào SQLite?
Mở cơ sở dữ liệu bằng CLI sqlite3, chuyển sang chế độ CSV với .mode csv, rồi chạy .import data.csv table_name. Nếu bảng chưa tồn tại, SQLite sẽ tạo bảng và dùng dòng đầu của tệp làm tên cột. Nếu bảng đã có, mọi dòng trong tệp đều được thêm vào như dữ liệu, nên thường bạn cần .import --skip 1 để bỏ qua dòng tiêu đề.
Làm sao nhập CSV có dòng tiêu đề vào một bảng SQLite đã có?
Dùng .import --csv --skip 1 data.csv table_name. Cờ --skip 1 bảo SQLite bỏ qua dòng đầu tiên để dòng tiêu đề không trở thành một hàng dữ liệu. Không có cờ này, bạn sẽ có một hàng chứa đúng tên các cột.
Vì sao nhập CSV vào SQLite báo lỗi 'expected N columns but found M'?
Tệp có những dòng mà số cột khác với bảng, thường do dấu phẩy nằm trong dữ liệu, dấu ngoặc kép không được escape, hoặc một dòng trống ở cuối. Dùng .mode csv (hoặc --csv) thay vì .mode tabs để SQLite xử lý dấu ngoặc kép theo RFC 4180, và mở tệp bằng trình soạn thảo văn bản để tìm ký tự phân tách thừa. CLI in ra số dòng gây lỗi, đó là cách nhanh nhất để tìm ra dòng hỏng.