Episode ini membahas Data Manipulation Language (DML): INSERT untuk memasukkan data, UPDATE untuk mengubah, dan DELETE untuk menghapus. Selain itu dibahas fitur khas PostgreSQL yaitu RETURNING clause untuk mengambil data yang baru dimanipulasi secara langsung.

Selamat datang di episode 4 series Belajar SQL PostgreSQL! Pada episode 3, kita sudah membangun struktur tabel yang ketat dengan tipe data dan constraint yang tepat. Sekarang saatnya memberi "nyawa" pada struktur tersebut: mengisinya dengan data dan memanipulasinya. Inilah bagian SQL yang paling sering kalian pakai sehari-hari sebagai developer.
Bahasa yang kita pelajari hari ini bernama DML (Data Manipulation Language). Berbeda dengan DDL yang mengelola struktur database, DML mengelola data di dalamnya: INSERT untuk memasukkan baris baru, UPDATE untuk mengubah baris yang ada, dan DELETE untuk menghapus baris. Bersama SELECT yang akan kita dalami di episode 5, keempat perintah ini adalah 90% dari pekerjaan SQL sehari-hari.
Satu hal yang membuat episode ini spesial adalah fitur khas PostgreSQL: RETURNING clause. Dengan fitur ini, kalian bisa langsung mengambil data yang baru saja di-insert, di-update, atau di-delete tanpa harus menjalankan query SELECT tambahan. Fitur kecil ini menghemat banyak round-trip dan sering menjadi pembeda PostgreSQL dengan database lain.
Perintah INSERT INTO adalah cara standar untuk menambahkan baris ke tabel. PostgreSQL mendukung beberapa pola yang perlu kalian kuasai.
INSERT INTO users (email, full_name, age)
VALUES ('budi@example.com', 'Budi Santoso', 25);Perhatikan bahwa kolom id dan created_at tidak perlu diisi karena memiliki default (identity dan now()). Daftar kolom di depan VALUES menentukan pemetaan nilai — kebiasaan baik yang membuat query tetap jelas meskipun struktur tabel berubah.
PostgreSQL memungkinkan memasukkan banyak baris sekaligus dalam satu perintah — jauh lebih efisien daripada menjalankan banyak insert terpisah:
INSERT INTO users (email, full_name, age)
VALUES
('sari@example.com', 'Sari Wulandari', 30),
('andi@example.com', 'Andi Pratama', 22),
('dewi@example.com', 'Dewi Lestari', 28);Satu perintah, tiga baris. Jika satu baris saja gagal (misal melanggar constraint), seluruh batch ikut gagal — ini konsisten dengan prinsip atomicity yang akan kita bahas di episode 11.
Pola yang sangat powerful: mengambil data dari query lain untuk dimasukkan. Ini sering dipakai untuk arsip, migrasi, atau seeding data:
INSERT INTO active_users (email, full_name)
SELECT email, full_name
FROM users
WHERE is_active = TRUE;Query di atas menyalin semua user aktif ke tabel active_users. Ini juga berguna untuk membaca data dari file CSV melalui perintah \copy di psql.
Tip
Untuk memasukkan data dari file CSV, psql menyediakan perintah \copy users (email, full_name, age) FROM 'data.csv' DELIMITER ',' CSV HEADER. Perintah ini berjalan di sisi client dan tidak memerlukan permission superuser di server.
UPDATE mengubah nilai kolom pada baris yang memenuhi kondisi. Tanpa WHERE, seluruh baris tabel akan berubah — jadi selalu pastikan kondisi WHERE sudah benar.
UPDATE users
SET age = 26
WHERE email = 'budi@example.com';Beberapa kolom bisa diubah sekaligus, dan kita bisa memakai nilai dari ekspresi:
UPDATE products
SET price = price * 1.10,
updated_at = now()
WHERE category = 'elektronik';Perhatikan pola price = price * 1.10 — nilai baru bisa dihitung dari nilai lama kolom itu sendiri. Ini sangat umum untuk kenaikan harga, penambahan stok, atau operasi aritmetika lain.
Warning
Ini peringatan terpenting dalam episode ini: selalu tulis WHERE pada UPDATE dan DELETE terlebih dahulu sebelum menyalakannya. Kalian bisa melatih kebiasaan aman dengan menjalankan SELECT dulu dengan kondisi yang sama, memastikan baris yang terfilter benar, baru konversi ke UPDATE atau DELETE.
DELETE menghapus baris yang memenuhi kondisi WHERE. Jika WHERE dihilangkan, semua baris terhapus — pada episode 2 kita belajar bahwa untuk itu TRUNCATE jauh lebih cepat.
DELETE FROM users
WHERE email = 'budi@example.com';DELETE FROM orders
WHERE user_id IN (
SELECT id FROM users WHERE is_active = FALSE
);Contoh kedua menghapus semua order dari user yang sudah tidak aktif. Pola IN dengan subquery akan kita dalami di episode 7.
Inilah keunggulan PostgreSQL yang membuatnya disayang developer. RETURNING clause mengembalikan baris yang baru saja dimanipulasi sebagai hasil query — sehingga tidak perlu SELECT tambahan untuk mengetahui apa yang terjadi.
Bayangkan kalian insert user dan butuh id-nya untuk melanjutkan proses (misal membuat session). Di banyak database, kalian harus menjalankan query kedua. Di PostgreSQL, cukup satu:
INSERT INTO users (email, full_name, age)
VALUES ('rudi@example.com', 'Rudi Hartono', 27)
RETURNING id, created_at;Hasilnya langsung berupa satu baris berisi id dan created_at dari data yang baru saja di-insert. Tanpa RETURNING, kalian harus menebak id atau menjalankan query lagi.
RETURNING juga bekerja pada UPDATE dan DELETE — sangat berguna untuk audit atau logging:
UPDATE products
SET stock = stock - 2
WHERE id = '1c2d3e4f-0000-0000-0000-000000000001'
RETURNING id, name, stock;DELETE FROM users
WHERE email = 'dewi@example.com'
RETURNING id, email;Pada UPDATE, RETURNING mengembalikan baris setelah perubahan diterapkan. Pada DELETE, ia mengembalikan baris yang dihapus — informasi berharga untuk dimasukkan ke log atau tabel arsip.
Note
RETURNING bukan standar ANSI SQL — ia fitur khusus PostgreSQL yang juga diadopsi beberapa database modern seperti SQLite dan DuckDB. Kemampuan mengambil data hasil manipulasi dalam satu round-trip ini menghilangkan race condition kecil yang sering terjadi saat insert lalu select kembali (data bisa berubah di antara kedua query).
Di dunia nyata, INSERT sering bertabrakan dengan constraint UNIQUE. PostgreSQL menyediakan ON CONFLICT untuk menangani tabrakan ini secara elegan — memperbarui data yang sudah ada alih-alih gagal:
INSERT INTO users (email, full_name, age)
VALUES ('sari@example.com', 'Sari Wulandari', 31)
ON CONFLICT (email)
DO UPDATE SET age = EXCLUDED.age
RETURNING id, email, age;EXCLUDED merujuk ke baris yang gagal di-insert. Jadi jika email sari@example.com sudah ada, umurnya diperbarui menjadi 31. Pola "upsert" semacam ini sangat umum di produksi, misalnya untuk sinkronisasi data dari API eksternal.
| # | Kesalahan | Gejala | Solusi |
|---|---|---|---|
| 1 | UPDATE/DELETE tanpa WHERE | Semua baris berubah/terhapus | Selalu filter dengan WHERE terlebih dahulu |
| 2 | Insert nilai duplikat di kolom unik | Error duplicate key value violates unique constraint | Gunakan ON CONFLICT atau cek duplikat |
| 3 | Insert tipe data salah | Error column ... is of type integer but expression is of type text | Konversi tipe dengan ::TYPE atau perbaiki input |
| 4 | Lupa RETURNING | Harus query tambahan untuk ambil id | Manfaatkan RETURNING untuk hemat round-trip |
Satu lagi yang sering terjadi: lupa menambahkan RETURNING saat butuh data hasil manipulasi. Karena kita tidak selalu bisa memprediksi nilai GENERATED ALWAYS id, menjalankan SELECT terpisah adalah boros dan rawan race condition. Biasakan selalu memikirkan "apakah saya butuh hasilnya?" sebelum mengeksekusi.
Di episode 4 ini kita sudah menguasai DML: memasukkan data dengan INSERT (single, multi-row, dan dari SELECT), mengubah data dengan UPDATE, menghapus dengan DELETE, serta memanfaatkan fitur khas PostgreSQL RETURNING untuk mengambil hasil manipulasi secara langsung. Kita juga belajar menangani konflik constraint dengan ON CONFLICT.
Inti yang harus dibawa pulang:
INSERT mendukung single, multi-row, dan INSERT ... SELECT.WHERE sebelum UPDATE dan DELETE — ini kebiasaan penyelamat.RETURNING mengembalikan baris hasil manipulasi tanpa query tambahan.ON CONFLICT menangani tabrakan constraint dengan pola upsert.Di episode 5 selanjutnya, kita masuk ke perintah yang paling sering dieksekusi dalam hidup seorang developer: Filtering, Sorting, Paging & Aggregation Query — mulai dari SELECT, WHERE dengan operator komparasi, LIKE/ILIKE, ORDER BY, LIMIT dan OFFSET, hingga agregasi dengan GROUP BY dan HAVING. Data kalian sudah ada; sekarang saatnya mengolahnya menjadi informasi.