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:

python
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:

text
Baris ditambahkan: 2
id | nama | kategori | harga
11 | Mouse Wireless Ergonomis | gadget | 250000
12 | Keyboard Mekanik Kompak | gadget | 850000

Mari kita telaah baris-baris kode di atas:

  1. kursor = koneksi.execute(...): INSERT dengan dua kelompok nilai dijalankan; kursor menyimpan informasi hasilnya.
  2. VALUES (11, ...), (12, ...): tiap tanda kurung mewakili satu baris baru, dengan urutan kolom yang dideklarasikan sebelum VALUES.
  3. koneksi.commit(): menjadikan perubahan permanen — tanpa ini, perubahan berpotensi dibatalkan saat koneksi ditutup (dibedah pada Chapter Transaksi Database).
  4. kursor.rowcount: jumlah baris yang benar-benar tertambah, yaitu 2.

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:

python
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:

text
nama | harga
Kaos Polos Katun | 95000
Kemeja Flanel | 180000
Baris diperbarui: 2
nama | harga
Kaos Polos Katun | 104500
Kemeja Flanel | 198000

Hanya 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:

python
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:

text
Baris dihapus: 1
pesanan_id | produk_id | jumlah | subtotal

Tepat 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:

python
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:

text
id | nama | harga
10 | Buku Sains Data | 145000
Baris terdampak: 1
id | nama | harga
10 | Buku Sains Data | 160000

Produk 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.

sql
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.