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:

sql
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):

text
sqlite> .headers on
sqlite> .mode csv
sqlite> .once pelanggan.csv
sqlite> SELECT * FROM pelanggan ORDER BY id;
sqlite> .quit

Mari kita bedah perintahnya baris per baris:

  1. Baris 1 (.headers on): baris pertama output akan berisi nama kolom id,nama,kota — tanpa ini, penerima berkas harus menebak urutan kolom.
  2. Baris 2 (.mode csv): output diformat sebagai CSV dengan koma sebagai pemisah kolom.
  3. Baris 3 (.once pelanggan.csv): hasil query berikutnya ditulis ke berkas pelanggan.csv, bukan ke layar. Setelah satu query, output kembali ke layar — .output adalah versinya yang permanen.
  4. Baris 4 (SELECT ...): query pengisi berkas. ORDER BY memastikan urutan baris stabil.

Isi berkas hasilnya:

bash
cat pelanggan.csv

Output yang Dihasilkan:

text
id,nama,kota
1,Budi,Jakarta
2,Siti,Bandung
3,Ani,Surabaya
4,"Dewi, S.Kom",Yogyakarta

Nilai 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:

text
sqlite> .mode json
sqlite> .once pelanggan.json
sqlite> SELECT * FROM pelanggan ORDER BY id;
bash
cat pelanggan.json

Output yang Dihasilkan:

json
[{"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:

text
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;
4

Bedah perintah importnya:

  1. Opsi --csv: pemisah kolom koma dan baris baru sebagai penanda baris.
  2. Opsi --skip 1: lewati baris pertama berkas — baris judul id,nama,kota bukan data.
  3. 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:

text
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:

csv
kode,nama
001,Budi
002,Siti

Kolom kode sengaja memakai nol di depan. Import ke kolom bertipe INTEGER dan ke tabel otomatis memberi hasil berbeda:

text
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|text

Kolom 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 '', bukan NULL.
  • 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:

text
sqlite> .import --csv pelanggan.csv pelanggan_csv
sqlite> SELECT (SELECT count(*) FROM pelanggan) AS asli,
   ...>        (SELECT count(*) FROM pelanggan_csv) AS impor;
4|4

Angka 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.