Data yang kedaluwarsa, salah input, atau sudah tidak lagi dibutuhkan pada akhirnya harus dikeluarkan dari tabel. Perintah DELETE adalah alat untuk itu: menghapus baris yang ada tanpa mengubah struktur tabel ataupun baris lainnya. Pada Chapter ini kita akan menghapus satu baris dan beberapa baris sekaligus dengan WHERE, melihat langsung bagaimana foreign key melindungi baris induk dari penghapusan, mencoba efek ON DELETE CASCADE yang menjalar ke tabel anak, lalu menutup dengan demo terkendali DELETE tanpa WHERE dan satu fakta penting: penomoran AUTO_INCREMENT tidak pernah ikut ter-reset.

Menyiapkan Tabel Latihan #

Kita pakai database latihan belajar_dml dan satu tabel toko alat tulis berisi empat baris:

sql
CREATE DATABASE belajar_dml;
USE belajar_dml;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

Database changed
sql
CREATE TABLE produk (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nama VARCHAR(100) NOT NULL,
  harga DECIMAL(10,2) NOT NULL,
  stok INT NOT NULL DEFAULT 0
);

INSERT INTO produk (nama, harga, stok) VALUES
  ('Buku Tulis', 4500.00, 120),
  ('Pensil 2B', 3000.00, 200),
  ('Penghapus', 2500.00, 150),
  ('Stipo', 6000.00, 40);

SELECT * FROM produk ORDER BY id;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

+----+------------+---------+------+
| id | nama       | harga   | stok |
+----+------------+---------+------+
|  1 | Buku Tulis | 4500.00 |  120 |
|  2 | Pensil 2B  | 3000.00 |  200 |
|  3 | Penghapus  | 2500.00 |  150 |
|  4 | Stipo      | 6000.00 |   40 |
+----+------------+---------+------+
4 rows in set (0.00 sec)

Struktur tabel ini sama dengan chapter-chapter sebelumnya supaya fokus kita tetap pada perintah DELETE, bukan pada skema baru. Empat baris inilah korban-korban latihan kita, dan setiap penghapusan akan selalu kita konfirmasi dengan SELECT agar efeknya terlihat nyata.

Menghapus Satu Baris dengan WHERE #

Bentuk paling dasar DELETE menghapus baris-baris yang dipilih oleh klausa WHERE. Kita mulai dengan menghapus Stipo yang sudah tidak dijual lagi:

sql
DELETE FROM produk WHERE id = 4;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

Mari kita bedah setiap bagiannya:

  1. Bagian 1 (DELETE FROM produk): Keyword DELETE FROM diikuti nama tabel sumber baris yang akan dihapus. Berbeda dari INSERT dan UPDATE yang bekerja atas nilai, DELETE bekerja atas keberadaan baris — baris yang terpilih lenyap seluruhnya bersama seluruh kolomnya.
  2. Bagian 2 (WHERE id = 4): Klausa WHERE memilih baris mana yang dihapus — hanya baris dengan id 4. Klausa ini menggunakan mesin penyaringan yang sama dengan SELECT, sehingga semua operator perbandingan yang sudah kita kenal berlaku di sini.

Respons Query OK, 1 row affected menyatakan tepat satu baris hilang dari tabel. Kondisi tabel setelah penghapusan bisa kita baca kembali:

sql
SELECT id, nama, stok FROM produk ORDER BY id;

Output yang Dihasilkan:

text
+----+------------+------+
| id | nama       | stok |
+----+------------+------+
|  1 | Buku Tulis |  120 |
|  2 | Pensil 2B  |  200 |
|  3 | Penghapus  |  150 |
+----+------------+------+
3 rows in set (0.00 sec)

Baris Stipo sudah tidak ada, sementara tiga baris lainnya utuh dengan nilai yang tidak tersentuh. Penghapusan tidak meninggalkan jejak pada baris yang tidak ikut terpilih — inilah mengapa SELECT setelah DELETE adalah kebiasaan verifikasi yang murah.

Menghapus Beberapa Baris Sekaligus #

Klausa WHERE tidak dibatasi satu baris. Kondisi stok < 160 cocok untuk setiap baris yang memenuhi syarat, dan dua produk kita jatuh dalam rentang itu:

sql
DELETE FROM produk WHERE stok < 160;

Output yang Dihasilkan:

text
Query OK, 2 rows affected (0.00 sec)

Respons Query OK, 2 rows affected menunjukkan Buku Tulis (stok 120) dan Penghapus (stok 150) terhapus bersamaan dalam satu statement. Sekali lagi kita periksa kondisi akhir tabel:

sql
SELECT id, nama, stok FROM produk ORDER BY id;

Output yang Dihasilkan:

text
+----+-----------+------+
| id | nama      | stok |
+----+-----------+------+
|  2 | Pensil 2B |  200 |
+----+-----------+------+
1 row in set (0.00 sec)

Tersisa satu baris Pensil 2B. Perhatikan pola pentingnya: jumlah baris pada respons DELETE adalah satu-satunya konfirmasi resmi berapa baris yang hilang. Bila aplikasi kita bermaksud menghapus satu baris unik tetapi responsnya menyebut 2 rows affected, kondisi WHERE terlalu longgar dan baris tak dikehendaki ikut terhapus — periksa kondisinya sebelum data lainnya hilang.

Delete dan Foreign Key #

Selama ini kita menghapus dari tabel yang berdiri sendiri. Pada skema nyata, tabel saling terhubung lewat foreign key — dan DELETE adalah perintah yang paling sering berbenturan dengannya. Kita bangun dua tabel berelasi: pelanggan sebagai induk dan pesanan sebagai anak yang kolom id_pelanggan-nya merujuk ke pelanggan.id:

sql
CREATE TABLE pelanggan (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nama VARCHAR(100) NOT NULL
);

CREATE TABLE pesanan (
  id INT AUTO_INCREMENT PRIMARY KEY,
  id_pelanggan INT NOT NULL,
  keterangan VARCHAR(100) NOT NULL,
  FOREIGN KEY (id_pelanggan) REFERENCES pelanggan (id)
);

INSERT INTO pelanggan (nama) VALUES ('Budi'), ('Siti');

INSERT INTO pesanan (id_pelanggan, keterangan) VALUES
  (1, 'Buku Tulis 5 pack'),
  (1, 'Kertas HVS 1 rim'),
  (2, 'Pensil 2B 12 unit');

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

Sekarang kita coba menghapus pelanggan Budi yang masih memiliki dua pesanan:

sql
-- @error
DELETE FROM pelanggan WHERE id = 1;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`belajar_dml`.`pesanan`, CONSTRAINT `pesanan_ibfk_1` FOREIGN KEY (`id_pelanggan`) REFERENCES `pelanggan` (`id`))
Bye

Galat ERROR 1451 menolak penghapusan: baris pelanggan tersebut masih direferensikan oleh baris di tabel pesanan. Perilaku bawaan foreign key inilah yang menjaga konsistensi — tanpanya, pesanan Budi akan menunjuk ke pelanggan yang tidak ada (baris yatim). Kita konfirmasi bahwa tidak ada data yang berubah:

sql
SELECT id, id_pelanggan, keterangan FROM pesanan ORDER BY id;

Output yang Dihasilkan:

text
+----+--------------+-------------------+
| id | id_pelanggan | keterangan        |
+----+--------------+-------------------+
|  1 |            1 | Buku Tulis 5 pack |
|  2 |            1 | Kertas HVS 1 rim  |
|  3 |            2 | Pensil 2B 12 unit |
+----+--------------+-------------------+
3 rows in set (0.00 sec)

Solusi pertama adalah menghapus baris anaknya terlebih dahulu, baru baris induknya. Solusi kedua adalah mengubah perilaku foreign key saat induk dihapus dengan ON DELETE CASCADE: baris anak ikut terhapus otomatis. Kita bangun ulang tabel pesanan dengan opsi tersebut:

sql
DROP TABLE pesanan;

CREATE TABLE pesanan (
  id INT AUTO_INCREMENT PRIMARY KEY,
  id_pelanggan INT NOT NULL,
  keterangan VARCHAR(100) NOT NULL,
  FOREIGN KEY (id_pelanggan) REFERENCES pelanggan (id) ON DELETE CASCADE
);

INSERT INTO pesanan (id_pelanggan, keterangan) VALUES
  (1, 'Buku Tulis 5 pack'),
  (1, 'Kertas HVS 1 rim'),
  (2, 'Pensil 2B 12 unit');

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

Statement penghapusan yang sama kini berjalan mulus — dan kedua pesanan Budi ikut lenyap:

sql
DELETE FROM pelanggan WHERE id = 1;

SELECT id, id_pelanggan, keterangan FROM pesanan ORDER BY id;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

+----+--------------+-------------------+
| id | id_pelanggan | keterangan        |
+----+--------------+-------------------+
|  3 |            2 | Pensil 2B 12 unit |
+----+--------------+-------------------+
1 row in set (0.00 sec)

Demo Terkendali: DELETE Tanpa WHERE #

Seperti UPDATE, perintah DELETE tanpa WHERE berlaku untuk seluruh baris tabel. Kita isi ulang tabel produk terlebih dahulu supaya efeknya terlihat jelas:

sql
INSERT INTO produk (nama, harga, stok) VALUES
  ('Map Plastik', 8500.00, 35),
  ('Kertas HVS A4', 25000.00, 15);

SELECT COUNT(*) AS jumlah_baris FROM produk;

Output yang Dihasilkan:

text
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

+--------------+
| jumlah_baris |
+--------------+
|            3 |
+--------------+
1 row in set (0.00 sec)

Tabel berisi tiga baris — Pensil 2B yang tersisa plus dua produk baru. Sekarang kita jalankan statement paling berbahaya di chapter ini:

sql
DELETE FROM produk;

SELECT COUNT(*) AS jumlah_baris FROM produk;

Output yang Dihasilkan:

text
Query OK, 3 rows affected (0.00 sec)

+--------------+
| jumlah_baris |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)

Seluruh tiga baris hilang sekaligus (3 rows affected) dan tabel kini kosong. Pada tabel latihan, akibatnya hanya data contoh; pada tabel produksi berisi jutaan baris transaksi, statement yang sama memusnahkan semuanya dalam hitungan detik dan tanpa konfirmasi.

DELETE Tidak Me-reset AUTO_INCREMENT #

Fakta yang sering mengejutkan: tabel yang baru saja kita kosongkan “mengingat” nomor urut terakhirnya. Counter AUTO_INCREMENT tetap berjalan maju meskipun seluruh baris sudah tidak ada:

sql
INSERT INTO produk (nama, harga) VALUES ('Stipo', 6000.00);

SELECT id, nama FROM produk ORDER BY id;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

+----+-------+
| id | nama  |
+----+-------+
|  7 | Stipo |
+----+-------+
1 row in set (0.00 sec)

Baris baru mendapat id 7 — lanjut dari nomor tertinggi yang pernah terpakai (empat baris awal plus dua baris demo), bukan mulai dari 1. Counter AUTO_INCREMENT memang tidak pernah mundur oleh DELETE: penghapusan hanya menjalangkan barisnya, sementara mesin penomoran tetap mencatat posisi terakhir. Celah pada deretan id akibat penghapusan adalah hal yang normal dan tidak perlu diperbaiki. Bila kita benar-benar butuh penomoran mulai dari 1 lagi pada tabel kosong, ada satu perintah khusus untuk itu — TRUNCATE TABLE, yang kita bahas pada chapter terakhir section ini.

Membersihkan Database Latihan #

Latihan selesai. Tiga tabel — produk, pelanggan, dan pesanan — ikut terjatuh bersama database-nya:

sql
DROP DATABASE belajar_dml;

Output yang Dihasilkan:

text
Query OK, 3 rows affected (0.00 sec)

Referensi #

Kita kini menguasai empat operasi dasar manipulasi data: menambah, membaca, mengubah, dan menghapus. Perjalanan berikutnya menyempurnakan cara kita menambahkan data: daripada mengirim INSERT satu baris demi satu baris, MySQL menerima ratusan baris dalam satu statement — chapter INSERT Multiple Rows membahas bentuk lengkapnya, urutan penomoran AUTO_INCREMENT pada batch, hingga sifat atomik yang membuat satu baris gagal menggagalkan seluruh batch.