Belajar Backend Developer - SQL & Database Dasar
Episode 5 of 28

Belajar Backend Developer - SQL & Database Dasar

Membangun fondasi data: desain skema relasional untuk toko online, menulis query yang benar, memahami indexing untuk performa, dan menggunakan transaction untuk konsistensi data dalam operasi multi-langkah

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Setelah di episode 4 kalian mendesain kontrak API, sekarang kita membangun rumah data — database. API yang indah tidak ada artinya jika data di belakangnya berantakan: query lambat, skema tidak konsisten, atau order yang hilang di tengah proses. SQL dan relational database adalah fondasi yang paling banyak dipakai di dunia backend, dan di 2026 masih tetap dominan untuk data yang membutuhkan konsistensi.

Mengapa harus paham SQL dalam-dalam? Karena kalian akan menulis query sepanjang karier — dan sebagian besar masalah performa backend adalah masalah query, bukan masalah bahasa pemrograman. Episode ini membangun empat fondasi: modeling skema, query, indexing, dan transaction. Database lab PostgreSQL dari episode 0 akan kita pakai sekarang.

Modeling Skema

Skema yang baik lahir dari memahami relasi antar entitas. Untuk toko online kita:

text
users ───┐
         ├── orders ──── order_items ──── products
reviews ─┘                (pivot)
  • users dan orders: relasi one-to-many (satu user banyak order).
  • orders dan products: relasi many-to-many lewat tabel pivot order_items.

Membangun Skema

schema.sql - skema awal toko online
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(100) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
 
CREATE TABLE products (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price NUMERIC(12,2) NOT NULL CHECK (price > 0),
    stock INTEGER NOT NULL DEFAULT 0 CHECK (stock >= 0),
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
 
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL REFERENCES users(id),
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
    total NUMERIC(12,2) NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
 
CREATE TABLE order_items (
    id BIGSERIAL PRIMARY KEY,
    order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
    product_id BIGINT NOT NULL REFERENCES products(id),
    qty INTEGER NOT NULL CHECK (qty > 0),
    unit_price NUMERIC(12,2) NOT NULL
);

Perhatikan keputusan penting di sini:

  • Primary key BIGSERIAL — identitas unik otomatis.
  • Foreign key (REFERENCES) — menjamin referensi valid, menjaga integritas.
  • CHECK constraint — mencegah data mustahil (harga negatif, stok minus).
  • ON DELETE CASCADE — hapus order otomatis menghapus item-nya.
  • NUMERIC(12,2) untuk uang — jangan pernah pakai FLOAT untuk harga (episode 25).

Query

Membaca dan Memfilter

Query dasar
SELECT id, name, price FROM products WHERE price > 100000 ORDER BY price DESC;
 
SELECT p.id, p.name, SUM(oi.qty) AS total_terjual
FROM products p
JOIN order_items oi ON oi.product_id = p.id
GROUP BY p.id, p.name
HAVING SUM(oi.qty) > 10
ORDER BY total_terjual DESC;

JOIN menggabungkan tabel, GROUP BY mengelompokkan, HAVING memfilter hasil agregasi (bukan WHERE, yang memfilter baris sebelum agregasi).

INSERT dengan Return

Insert lalu ambil id yang dibuat
INSERT INTO products (name, price, stock)
VALUES ('Mouse Wireless', 150000, 50)
RETURNING id, name;

RETURNING menghemat satu query — sangat berguna saat API perlu mengembalikan resource yang baru dibuat dengan 201 Created.

Indexing

Tanpa index, PostgreSQL membaca seluruh tabel untuk menemukan baris — disebut sequential scan. Index membuat pencarian seperti mencari kata di kamus alih-alih membaca semua halaman.

Index untuk query yang sering dipakai
CREATE INDEX idx_products_price ON products (price);
CREATE INDEX idx_orders_user ON orders (user_id);
CREATE INDEX idx_orders_status ON orders (status);
CREATE INDEX idx_order_items_product ON order_items (product_id);

Prinsip index:

  • Index untuk kolom yang dipakai di WHERE, JOIN, dan ORDER BY — bukan semua kolom.
  • Setiap index menambah biaya pada tiap insert/update — jangan berlebihan.
  • Composite index untuk filter gabungan: CREATE INDEX idx ON products (category_id, price); memakai kolom kiri-ke-kanan.
  • Uji dengan EXPLAIN ANALYZE sebelum dan sesudah.
EXPLAIN ANALYZE - sebelum dan sesudah index
Seq Scan on products  (cost=0.00..1200.50 rows=10000)   -- lambat
Bitmap Index Scan on idx_products_price (cost=0.00..6.20 rows=50)  -- cepat

Note

EXPLAIN ANALYZE adalah alat nomor satu untuk diagnosa query lambat — ia menjalankan query dan menunjukkan plan eksekusi serta waktu nyata. Biasakan melihatnya setiap kali API melambat. Kita dalami ini di episode 25 saat performance tuning.

Transaction

Beberapa operasi harus berhasil atau gagal bersama-sama. Contoh: saat order dibuat, kita harus (1) kurangi stok produk, (2) buat order, (3) buat item-nya. Jika langkah 2 gagal setelah langkah 1, stok jadi salah — inilah yang dicegah transaction.

Transaction untuk checkout
BEGIN;
 
UPDATE products SET stock = stock - 2 WHERE id = 1 AND stock >= 2;
IF NOT FOUND THEN ROLLBACK; END IF;
 
INSERT INTO orders (user_id, status, total)
VALUES (5, 'pending', 300000)
RETURNING id \gset
 
INSERT INTO order_items (order_id, product_id, qty, unit_price)
VALUES (:'id', 1, 2, 150000);
 
COMMIT;

Sifat transaction disingkat ACID:

  • Atomicity — semua atau tidak sama sekali.
  • Consistency — data selalu valid sesuai constraint.
  • Isolation — transaction tidak saling melihat data setengah jadi.
  • Durability — setelah commit, data tidak hilang meski server crash.

Race condition yang harus kalian waspadai: dua request checkout stok terakhir secara bersamaan. Solusinya di SQL adalah conditional update WHERE stock >= 2 di atas — update hanya berhasil jika stok cukup. Episode 25 membahas isolation level lebih dalam.

Warning

Pengecekan stok hanya di aplikasi (cek lalu update) tidak aman tanpa transaction atau conditional update — dua request bersamaan bisa sama-sama lolos. Selalu jadikan pengecekan dan pengurangan stok satu operasi atomik di database.

Praktik: Uji di Database Lab

Muat skema ke PostgreSQL lab dan coba:

Eksekusi schema dan cek
docker exec -i backend-lab psql -U dev -d shop < schema.sql
docker exec -i backend-lab psql -U dev -d shop \
  -c "INSERT INTO products (name, price, stock) VALUES ('Kabel USB', 50000, 100) RETURNING id;"
docker exec -i backend-lab psql -U dev -d shop \
  -c "EXPLAIN ANALYZE SELECT * FROM products WHERE price > 100000;"

Common Pitfalls

SELECT Tanpa LIMIT pada Data Besar

List tanpa batas akan makin lambat seiring data bertambah. Selalu kombinasikan dengan pagination (episode 4).

Menyimpan Uang sebagai FLOAT

FLOAT tidak bisa merepresentasikan desimal dengan tepat — 0.1 + 0.2 bisa jadi 0.30000000000000004. Uang harus NUMERIC/DECIMAL.

N+1 Query

Loop di aplikasi yang mengeksekusi satu query per baris hasil. Solusi: JOIN atau batch query — topik episode 9 dan 25.

Penutup

Episode 5 membangun rumah data: skema relasional dengan constraint yang menjaga integritas, query yang memakai JOIN dan agregasi, index untuk performa, dan transaction ACID untuk konsistensi.

Inti yang harus dibawa pulang:

  • Skema = entitas + relasi + constraint; uang selalu NUMERIC, bukan FLOAT.
  • JOIN + GROUP BY + HAVING adalah trias query analitis.
  • Index untuk kolom WHERE/JOIN/ORDER BY; uji dengan EXPLAIN ANALYZE.
  • Transaction (ACID) memastikan operasi multi-langkah berhasil atau gagal bersama.
  • Cegah race condition stok dengan conditional update atomik.

Di episode 6 selanjutnya kita akan membandingkan dunia kedua: NoSQL & pilihan database — document store seperti MongoDB, key-value seperti Redis, dan kapan memilih relational vs NoSQL untuk kasus nyata. Sampai jumpa di episode 6!

Belajar Backend Developer - SQL & Database Dasar | Belajar Backend