Relasi paling umum dalam database adalah one-to-many: satu pelanggan boleh memiliki banyak pesanan, tetapi satu pesanan hanya milik satu pelanggan. Membangun relasi seperti ini di SQLite bukan sekadar menambah kolom — kita harus memutuskan tabel mana yang memegang kolom penghubung, cara menggabungkan kedua tabel saat membaca data, dan apa yang terjadi jika referensi menunjuk ke baris yang tidak ada. Chapter ini membedah ketiganya memakai pasangan tabel pelanggan dan pesanan.
Skema Contoh #
Sepanjang chapter ini kita memakai dua tabel berikut. Kolom pelanggan_id pada tabel pesanan adalah penghubung ke pelanggan.id:
CREATE TABLE pelanggan (
id INTEGER PRIMARY KEY,
nama TEXT NOT NULL,
kota TEXT
);
CREATE TABLE pesanan (
id INTEGER PRIMARY KEY,
tanggal TEXT NOT NULL,
pelanggan_id INTEGER REFERENCES pelanggan(id),
total INTEGER NOT NULL
);
INSERT INTO pelanggan VALUES
(1, 'Budi', 'Jakarta'),
(2, 'Siti', 'Bandung'),
(3, 'Ani', 'Jakarta');
INSERT INTO pesanan VALUES
(100, '2025-01-05', 1, 90000),
(101, '2025-01-06', 1, 25000),
(102, '2025-01-07', 2, 18000);Perhatikan distribusi datanya: Budi memiliki dua pesanan, Siti satu, dan Ani belum memiliki pesanan sama sekali — variasi inilah yang membuat relasi one-to-many terlihat.
Kolom Penghubung Ditaruh di Sisi “Many” #
Kolom penghubung harus berada di tabel anak (child), yaitu sisi yang berjumlah banyak. Alasannya mekanis: kalau pelanggan yang memegang daftar pesanan, satu baris pelanggan harus menyimpan banyak id sekaligus — kolom seperti daftar_pesanan berteks '100, 101' melanggar aturan nilai atomik dari chapter Normalisasi Dasar, dan jumlah pesanan yang bisa dicatat menjadi tidak terbatas.
| Desain | Konsekuensi |
|---|---|
pesanan.pelanggan_id menunjuk pelanggan.id | Satu baris pesanan menyimpan satu nilai id; pelanggan boleh dirujuk banyak pesanan |
pelanggan.daftar_pesanan berteks | Nilai tidak atomik; tidak bisa di-JOIN, tidak bisa diindeks |
Dengan kolom di sisi anak, satu baris pesanan menyimpan tepat satu referensi, dan sisi induk tetap bersih tanpa perlu tahu siapa anaknya.
Menggabungkan Kedua Sisi dengan JOIN #
Setelah skema berdiri, pertanyaan “pesanan mana milik siapa” dijawab dengan JOIN melalui kolom penghubung:
SELECT p.nama, o.id, o.tanggal, o.total
FROM pelanggan p
JOIN pesanan o ON o.pelanggan_id = p.id
ORDER BY o.id;Output yang Dihasilkan:
Budi|100|2025-01-05|90000
Budi|101|2025-01-06|25000
Siti|102|2025-01-07|18000Bedah singkat query di atas:
- Baris 3 (
JOIN pesanan o ON o.pelanggan_id = p.id): engine SQLite memasangkan setiap barispesanandengan barispelangganyangid-nya sama denganpelanggan_id— inilah titik relasi didefinisikan. - Baris 1 (
SELECT p.nama, o.id, ...): kolom dari kedua tabel dicampur dalam satu hasil; nama pelanggan berulang karena satu pelanggan muncul sekali per pesanannya.
Agregat per pelanggan juga mengalir lewat relasi yang sama. Query berikut memakai LEFT JOIN supaya Ani tetap muncul meskipun tidak punya pesanan:
SELECT p.nama, count(o.id) AS jumlah_pesanan, sum(o.total) AS total_belanja
FROM pelanggan p
LEFT JOIN pesanan o ON o.pelanggan_id = p.id
GROUP BY p.id
ORDER BY p.id;Output yang Dihasilkan:
Budi|2|115000
Siti|1|18000
Ani|0|count(o.id) menghasilkan 0 untuk Ani karena LEFT JOIN mengisi baris kosong dengan NULL, dan count(kolom) melewatkan NULL. sum(o.total) menghasilkan NULL — bukan nol — karena tidak ada satu pun nilai yang dijumlahkan.
Baris Yatim Saat Penegakan FK Mati #
Skema kita sudah memakai REFERENCES pelanggan(id), tetapi SQLite secara bawaan tidak menegakkan foreign key. Percobaan berikut membuktikannya — INSERT merujuk pelanggan nomor 99 yang tidak pernah ada:
INSERT INTO pesanan VALUES (103, '2025-01-08', 99, 75000);Statement tersebut berhasil tanpa galat. Pesanan 103 kini menjadi baris yatim (orphan row), terlihat sebagai nama kosong pada LEFT JOIN:
SELECT o.id, o.pelanggan_id, p.nama
FROM pesanan o
LEFT JOIN pelanggan p ON o.pelanggan_id = p.id
ORDER BY o.id;Output yang Dihasilkan:
100|1|Budi
101|1|Budi
102|2|Siti
103|99|Lebih berbahaya lagi, JOIN biasa membuang baris yatim secara diam-diam sehingga totalnya bisa bohong:
SELECT count(*) FROM pesanan o
JOIN pelanggan p ON o.pelanggan_id = p.id;Output yang Dihasilkan:
3Tabel pesanan berisi 4 baris, tetapi hasil query hanya 3 — uang sebesar 75000 dari pesanan yatim tidak pernah masuk hitungan.
Desain one-to-many menuntaskan relasi “satu induk, banyak anak”. Kasus berikutnya lebih rumit: satu pesanan boleh memuat banyak produk, dan satu produk boleh masuk banyak pesanan — relasi many-to-many yang dibahas pada chapter Merancang Relasi Many-to-Many.