Sejauh ini seluruh query kita berada dalam mode baca. Pipeline data nyata bekerja dua arah: skrip ingestion memuat ribuan baris baru setiap hari, proses pembersihan memperbaiki nilai keliru, dan data kedaluwarsa harus disingkirkan. SQL menyediakan tiga perintah inti untuk pekerjaan itu — INSERT, UPDATE, DELETE — plus pola UPSERT yang membuat pipeline aman dijalankan berulang. Chapter ini menguji semuanya secara nyata di SQLite pada database toko online dari Chapter Common Table Expressions.
INSERT dengan Multi Nilai #
Satu pernyataan INSERT mampu memuat banyak baris sekaligus — cukup pisahkan tiap kelompok nilai dengan koma. Bentuk ini jauh lebih efisien daripada satu INSERT per baris karena database cukup menyiapkan eksekusi satu kali:
from setup_toko import buat_database, tampilkan
koneksi = buat_database()
kursor = koneksi.execute("""
INSERT INTO produk (id, nama, kategori, harga) VALUES
(11, 'Mouse Wireless Ergonomis', 'gadget', 250000),
(12, 'Keyboard Mekanik Kompak', 'gadget', 850000)
""")
koneksi.commit()
print("Baris ditambahkan:", kursor.rowcount)
tampilkan(koneksi, "SELECT id, nama, kategori, harga FROM produk WHERE id >= 11")Output yang Dihasilkan:
Baris ditambahkan: 2
id | nama | kategori | harga
11 | Mouse Wireless Ergonomis | gadget | 250000
12 | Keyboard Mekanik Kompak | gadget | 850000Mari kita telaah baris-baris kode di atas:
kursor = koneksi.execute(...):INSERTdengan dua kelompok nilai dijalankan; kursor menyimpan informasi hasilnya.VALUES (11, ...), (12, ...): tiap tanda kurung mewakili satu baris baru, dengan urutan kolom yang dideklarasikan sebelumVALUES.koneksi.commit(): menjadikan perubahan permanen — tanpa ini, perubahan berpotensi dibatalkan saat koneksi ditutup (dibedah pada Chapter Transaksi Database).kursor.rowcount: jumlah baris yang benar-benar tertambah, yaitu2.
UPDATE dengan WHERE #
UPDATE mengubah nilai kolom pada baris yang dipilih. Kunci keamanannya ada pada klausul WHERE: baris mana pun yang tidak lolos filter dibiarkan utuh. Contoh berikut menaikkan harga produk kategori pakaian sebesar sepuluh persen:
from setup_toko import buat_database, tampilkan
koneksi = buat_database()
tampilkan(koneksi, "SELECT nama, harga FROM produk WHERE kategori = 'pakaian' ORDER BY nama")
kursor = koneksi.execute("""
UPDATE produk
SET harga = CAST(ROUND(harga * 1.1) AS INTEGER)
WHERE kategori = 'pakaian'
""")
koneksi.commit()
print("Baris diperbarui:", kursor.rowcount)
tampilkan(koneksi, "SELECT nama, harga FROM produk WHERE kategori = 'pakaian' ORDER BY nama")Output yang Dihasilkan:
nama | harga
Kaos Polos Katun | 95000
Kemeja Flanel | 180000
Baris diperbarui: 2
nama | harga
Kaos Polos Katun | 104500
Kemeja Flanel | 198000Hanya dua baris kategori pakaian yang berubah: 95000 menjadi 104500 dan 180000 menjadi 198000. ROUND(harga * 1.1) membulatkan hasil desimal, lalu CAST(... AS INTEGER) menyimpan hasilnya sebagai integer sesuai tipe kolom harga.
DELETE dengan WHERE #
DELETE menghapus baris dengan pola filter yang sama seperti UPDATE. Kasus pembersihannya sudah menunggu sejak Chapter Conditional Expressions: detail pesanan 5 — pesanan yang dibatalkan — menyimpan baris dengan subtotal NULL:
from setup_toko import buat_database, tampilkan
koneksi = buat_database()
kursor = koneksi.execute("""
DELETE FROM detail_pesanan
WHERE subtotal IS NULL
""")
koneksi.commit()
print("Baris dihapus:", kursor.rowcount)
tampilkan(koneksi, """
SELECT pesanan_id, produk_id, jumlah, subtotal
FROM detail_pesanan
WHERE pesanan_id = 5
""")Output yang Dihasilkan:
Baris dihapus: 1
pesanan_id | produk_id | jumlah | subtotalTepat satu baris tersingkir; pesanan 5 kini tidak memuat baris detail apa pun — hanya nama kolom yang tercetak. Untuk mengosongkan seluruh tabel, PostgreSQL menyediakan TRUNCATE TABLE yang lebih cepat karena melewati proses per-baris — perintah ini tidak ada di SQLite, padanannya DELETE tanpa WHERE.
UPSERT: Insert atau Update dalam Satu Langkah #
Pipeline ingestion kerap dijalankan ulang — jadwal terlewat, data sumber dikirim dua kali. INSERT biasa untuk data yang sudah ada memicu pelanggaran kunci utama dan menggagalkan proses. UPSERT menyelesaikannya: INSERT ... ON CONFLICT memutuskan sendiri apakah baris perlu ditambahkan atau diperbarui:
from setup_toko import buat_database, tampilkan
koneksi = buat_database()
tampilkan(koneksi, "SELECT id, nama, harga FROM produk WHERE id = 10")
kursor = koneksi.execute("""
INSERT INTO produk (id, nama, kategori, harga)
VALUES (10, 'Buku Sains Data', 'buku', 160000)
ON CONFLICT (id) DO UPDATE
SET harga = excluded.harga
""")
koneksi.commit()
print("Baris terdampak:", kursor.rowcount)
tampilkan(koneksi, "SELECT id, nama, harga FROM produk WHERE id = 10")Output yang Dihasilkan:
id | nama | harga
10 | Buku Sains Data | 145000
Baris terdampak: 1
id | nama | harga
10 | Buku Sains Data | 160000Produk id = 10 sudah ada, sehingga ON CONFLICT (id) DO UPDATE SET harga = excluded.harga memperbarui harga dari 145000 menjadi 160000 alih-alih gagal. Kata kunci excluded merujuk nilai baru yang gagal dimasukkan, dan target konflik (id) wajib dilindungi kunci utama atau batasan unik. Varian DO NOTHING membiarkan baris lama utuh — pada pengujian nyata, varian ini melaporkan rowcount 0 dan harga tetap 145000. Sintaks ON CONFLICT identik di SQLite dan PostgreSQL; MySQL memakai dialek ON DUPLICATE KEY UPDATE.
RETURNING: Hasil Tanpa Query Ulang #
Fitur yang diperkenalkan PostgreSQL mempersingkat langkah terakhir: klausa RETURNING membuat pernyataan tulis mengembalikan baris yang terdampak, layaknya hasil SELECT.
INSERT INTO produk (id, nama, kategori, harga)
VALUES (11, 'Mouse Wireless Ergonomis', 'gadget', 250000)
RETURNING id, nama, harga;Saat dijalankan di PostgreSQL, pernyataan tersebut mengembalikan satu baris berisi id, nama, dan harga yang baru dimasukkan — skrip ingestion memperoleh konfirmasi tanpa SELECT terpisah. Klausa ini juga berlaku pada UPDATE dan DELETE, sehingga pola “ubah lalu baca ulang” pada contoh UPDATE di atas cukup dilakukan dalam satu permintaan. SQLite 3.35 ke atas turut mendukung RETURNING; dokumentasi rujukannya adalah SQL Commands PostgreSQL
dan perintah tulis SQLite
.
Tiga perintah tulis ditambah UPSERT kini lengkap. Namun contoh sebelumnya menyimpan risiko: UPDATE pertama berhasil lalu proses berhenti di tengah jalan, dan tabel tertinggal setengah berubah. Chapter berikutnya, Transaksi Database, memperkenalkan BEGIN, COMMIT, dan ROLLBACK yang menjamin rangkaian perubahan utuh sepenuhnya atau tidak sama sekali.