Data jarang berhenti di satu tempat: analisis meminta berkas CSV untuk spreadsheet, sistem lain meminta JSON, dan migrasi data meminta import massal dari berkas teks. sqlite3 CLI membawa perangkat lengkap untuk kedua arah — .mode dan .once untuk export, .import untuk import — tanpa perlu tool eksternal. Bab ini mempraktikkan kedua arah tersebut lengkap dengan jebakan-jebakan klasiknya.
Export ke CSV #
Kita pakai tabel pelanggan di toko.db sebagai bahan latihan — perhatikan baris terakhir yang mengandung koma di dalam nilainya:
CREATE TABLE pelanggan (
id INTEGER PRIMARY KEY,
nama TEXT NOT NULL,
kota TEXT NOT NULL
);
INSERT INTO pelanggan (nama, kota) VALUES
('Budi', 'Jakarta'),
('Siti', 'Bandung'),
('Ani', 'Surabaya'),
('Dewi, S.Kom', 'Yogyakarta');Export dilakukan lewat tiga dot command berurutan di dalam sesi interaktif (dasar-dasar .mode dibahas pada chapter Dot Commands pada sqlite3 CLI):
sqlite> .headers on
sqlite> .mode csv
sqlite> .once pelanggan.csv
sqlite> SELECT * FROM pelanggan ORDER BY id;
sqlite> .quitMari kita bedah perintahnya baris per baris:
- Baris 1 (
.headers on): baris pertama output akan berisi nama kolomid,nama,kota— tanpa ini, penerima berkas harus menebak urutan kolom. - Baris 2 (
.mode csv): output diformat sebagai CSV dengan koma sebagai pemisah kolom. - Baris 3 (
.once pelanggan.csv): hasil query berikutnya ditulis ke berkaspelanggan.csv, bukan ke layar. Setelah satu query, output kembali ke layar —.outputadalah versinya yang permanen. - Baris 4 (
SELECT ...): query pengisi berkas.ORDER BYmemastikan urutan baris stabil.
Isi berkas hasilnya:
cat pelanggan.csvOutput yang Dihasilkan:
id,nama,kota
1,Budi,Jakarta
2,Siti,Bandung
3,Ani,Surabaya
4,"Dewi, S.Kom",YogyakartaNilai Dewi, S.Kom otomatis dibungkus tanda kutip ganda karena mengandung koma — engine menambahkan kutipan seperlunya sehingga berkas tetap valid dan bisa diparse ulang tanpa kehilangan kolom.
Export ke JSON #
Untuk konsumen berbasis API atau JavaScript, ganti mode-nya saja:
sqlite> .mode json
sqlite> .once pelanggan.json
sqlite> SELECT * FROM pelanggan ORDER BY id;cat pelanggan.jsonOutput yang Dihasilkan:
[{"id":1,"nama":"Budi","kota":"Jakarta"},
{"id":2,"nama":"Siti","kota":"Bandung"},
{"id":3,"nama":"Ani","kota":"Surabaya"},
{"id":4,"nama":"Dewi, S.Kom","kota":"Yogyakarta"}]Hasilnya adalah array JSON yang valid dengan satu objek per baris — cukup untuk dikonsumsi fetch() di browser atau json.loads() di Python.
Import dari CSV #
Ada dua jalur import, tergantung apakah tabel tujuan sudah ada. Jalur pertama: tabel sudah dibuat dengan skema eksplisit, lalu berkas diimpor dengan melewati baris judul:
sqlite> CREATE TABLE pelanggan_impor (
...> id INTEGER PRIMARY KEY,
...> nama TEXT NOT NULL,
...> kota TEXT NOT NULL
...> );
sqlite> .import --csv --skip 1 pelanggan.csv pelanggan_impor
sqlite> SELECT count(*) FROM pelanggan_impor;
4Bedah perintah importnya:
- Opsi
--csv: pemisah kolom koma dan baris baru sebagai penanda baris. - Opsi
--skip 1: lewati baris pertama berkas — baris judulid,nama,kotabukan data. - Argumen
pelanggan.csv pelanggan_impor: berkas sumber diikuti nama tabel tujuan, dalam urutan itu.
Jalur kedua: tabel tujuan belum ada, dan .import membuatnya otomatis dari baris pertama berkas:
sqlite> .import --csv pelanggan.csv pelanggan_otomatis
sqlite> .schema pelanggan_otomatis
CREATE TABLE IF NOT EXISTS "pelanggan_otomatis"(
"id" TEXT, "nama" TEXT, "kota" TEXT)Nama kolom diambil dari baris judul, tetapi semua kolomnya bertipe TEXT — jika kita butuh tipe, index, atau constraint, buat tabelnya sendiri lebih dulu lewat jalur pertama.
Jebakan Type Affinity #
CSV tidak punya tipe — semua isinya teks. Konsekuensinya tampak pada berkas kodepos.csv berikut:
kode,nama
001,Budi
002,SitiKolom kode sengaja memakai nol di depan. Import ke kolom bertipe INTEGER dan ke tabel otomatis memberi hasil berbeda:
sqlite> CREATE TABLE kodepos_int (kode INTEGER, nama TEXT);
sqlite> .import --csv --skip 1 kodepos.csv kodepos_int
sqlite> SELECT kode, typeof(kode) FROM kodepos_int;
1|integer
2|integer
sqlite> .import --csv kodepos.csv kodepos_teks
sqlite> SELECT kode, typeof(kode) FROM kodepos_teks;
001|text
002|textKolom INTEGER men-trigger type affinity: teks '001' dipaksa menjadi angka 1 dan nol depannya hilang selamanya (mekanisme lengkapnya dibahas pada chapter Tipe Data dan Type Affinity). Tabel otomatis yang serba TEXT justru mempertahankan format aslinya. Aturan praktisnya: jika format teks harus dipertahankan, deklarasikan kolomnya TEXT secara eksplisit.
Dua jebakan import lain yang serupa:
- Nilai kosong di CSV menjadi string kosong
'', bukanNULL. - Berkas buatan tangan yang tidak mengutip nilai ber-koma membuat satu baris terpecah menjadi kolom ekstra; sqlite3 tetap memasukkan barisnya dengan kelebihan kolom dibuang, disertai peringatan
expected 3 columns but found 4 - extras ignored.
Uji Pulang: Export lalu Import #
Latihan penutup yang sebaiknya jadi kebiasaan: hasil export harus bisa kembali utuh. Kita impor pelanggan.csv ke tabel baru lalu bandingkan jumlahnya:
sqlite> .import --csv pelanggan.csv pelanggan_csv
sqlite> SELECT (SELECT count(*) FROM pelanggan) AS asli,
...> (SELECT count(*) FROM pelanggan_csv) AS impor;
4|4Angka 4|4 menutup siklusnya: empat baris keluar sebagai CSV dan empat baris kembali sebagai tabel. Jika selisih muncul, biasanya berasal dari baris judul yang ikut terimpor atau nilai ber-koma yang tidak terkutip.
Untuk import yang butuh validasi ketat per baris — menolak duplikat, memvalidasi format, mencatat baris gagal — tool yang tepat adalah program kecil dengan modul sqlite3 Python, pola yang dibahas pada section SQLite dengan Python.