Satu pelanggan dapat membuat banyak pesanan. Satu pesanan memiliki banyak item. Satu produk bisa muncul pada banyak item pesanan.
Database relasional menyimpan fakta dalam tabel yang saling terhubung lewat key. Desain yang baik mengurangi duplikasi dan menjaga data tetap konsisten ketika aplikasi tumbuh.
Modelkan relasi sebelum menulis query
CREATE TABLE customers (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(120) NOT NULL,
email VARCHAR(190) NOT NULL UNIQUE
);
CREATE TABLE orders (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
customer_id BIGINT UNSIGNED NOT NULL,
status VARCHAR(30) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
CREATE TABLE order_items (
order_id BIGINT UNSIGNED NOT NULL,
product_id BIGINT UNSIGNED NOT NULL,
quantity INT UNSIGNED NOT NULL,
unit_price DECIMAL(12,2) NOT NULL,
PRIMARY KEY (order_id, product_id)
);Harga disalin ke order_items.unit_price karena pesanan adalah catatan historis. Jika harga produk berubah besok, total pesanan kemarin tidak boleh ikut berubah.
SELECT yang menjawab pertanyaan bisnis
SELECT
o.id,
c.name AS customer,
SUM(oi.quantity * oi.unit_price) AS total
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
WHERE o.created_at >= CURRENT_DATE - INTERVAL 30 DAY
GROUP BY o.id, c.name
ORDER BY total DESC;Normalisasi secukupnya
| Masalah | Contoh buruk | Perbaikan |
|---|---|---|
| Data berulang | Nama pelanggan disimpan di setiap pesanan | Simpan customer_id dan gunakan relasi. |
| Nilai majemuk | Kolom produk berisi “1,4,9” | Buat tabel order_items. |
| Atribut bergantung sebagian | Nama produk di tabel dengan primary key gabungan | Pindahkan ke tabel products. |
| Riwayat berubah | Total dihitung dari harga produk terkini | Simpan unit_price saat transaksi. |
Transaction menjaga perubahan tetap utuh
Pembuatan pesanan, pengurangan stok, dan penyimpanan item harus berhasil bersama. Bila salah satu gagal, seluruh operasi dibatalkan.
START TRANSACTION;
INSERT INTO orders (customer_id, status) VALUES (42, 'pending');
SET @order_id = LAST_INSERT_ID();
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES (@order_id, 7, 2, 125000);
UPDATE products SET stock = stock - 2 WHERE id = 7 AND stock >= 2;
COMMIT;SQL yang baik dimulai dari model data yang benar. Query canggih tidak dapat sepenuhnya menyelamatkan struktur tabel yang keliru.
