Primary key menjaga identitas baris, tetapi identitas bukan satu-satunya data yang tidak boleh kembar: satu alamat email hanya boleh dimiliki satu akun, dan satu kombinasi kota-nama hanya boleh dipakai satu cabang toko. Kebutuhan seperti itu tidak pantas memakai primary key — email bukan identitas teknis tabel — dan di sinilah UNIQUE berperan: constraint yang menolak nilai duplikat pada satu kolom atau gabungan kolom, dengan syarat lebih longgar daripada primary key. Pada chapter ini kita akan menguji UNIQUE pada kolom email dan pasangan kolom kota-nama, menyaksikan galat 1062 saat duplikat masuk, dan membuktikan satu kejutan klasik: kolom UNIQUE ternyata masih menerima banyak baris NULL.

Menjaga Keunikan Satu Kolom #

Kita siapkan database latihan dan tabel pengguna untuk aplikasi dengan akun. Kolom email wajib unik — dua akun tidak boleh memakai email yang sama — sedangkan kolom telepon kita jadikan contoh kolom unik yang opsional:

sql
CREATE DATABASE belajar_unik;
USE belajar_unik;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

Database changed
sql
CREATE TABLE pengguna (
  id INT PRIMARY KEY,
  email VARCHAR(100) NOT NULL UNIQUE,
  nama VARCHAR(100) NOT NULL,
  telepon VARCHAR(20) UNIQUE
);

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Mari kita bedah baris-baris yang membawa constraint:

  1. Baris 2 (id INT PRIMARY KEY): Identitas teknis tetap dipegang kolom id — pola kunci sintetis dari chapter sebelumnya.
  2. Baris 3 (email VARCHAR(100) NOT NULL UNIQUE): Kata kunci UNIQUE menjadikan nilai email unik di seluruh tabel. Kombinasi NOT NULL UNIQUE berarti email wajib diisi dan tidak boleh kembar.
  3. Baris 5 (telepon VARCHAR(20) UNIQUE): Tanpa NOT NULL, kolom ini unik hanya untuk nilai yang terisi — aturan khusus untuk NULL akan kita buktikan sebentar lagi.

Klausa UNIQUE otomatis membangun index di belakang layar, sehingga pengecekan duplikat dilakukan dengan cepat meski tabel berisi jutaan baris. Kita isi dua akun pertama:

sql
INSERT INTO pengguna (id, email, nama, telepon) VALUES
  (1, '[email protected]', 'Budi', '081211112222'),
  (2, '[email protected]', 'Siti', NULL);

SELECT id, email, nama, telepon FROM pengguna ORDER BY id;

Output yang Dihasilkan:

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

+----+------------------+------+--------------+
| id | email            | nama | telepon      |
+----+------------------+------+--------------+
|  1 | [email protected] | Budi | 081211112222 |
|  2 | [email protected] | Siti | NULL         |
+----+------------------+------+--------------+
2 rows in set (0.00 sec)

Duplikat Ditolak dengan Galat 1062 #

Sekarang akun ketiga mencoba mendaftar dengan email yang sudah dipakai Budi:

sql
-- @error
INSERT INTO pengguna (id, email, nama, telepon) VALUES (3, '[email protected]', 'Ani', NULL);

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

ERROR 1062 (23000): Duplicate entry '[email protected]' for key 'pengguna.email'
Bye

Server menolak pendaftaran itu dengan galat ERROR 1062 (23000): Duplicate entry '[email protected]' for key 'pengguna.email' — pesan yang sama dengan pelanggaran primary key, hanya nama penjaganya kini pengguna.email, mengikuti index unik kolom email. Baris Ani tidak tersimpan sama sekali, sehingga pemilik akun asli tetap satu.

Kejutan NULL yang Boleh Ganda #

Inilah perilaku yang paling sering mengejutkan pemula: kolom UNIQUE masih menerima banyak baris NULL. Nilai NULL berarti “tidak diketahui”, dan dua nilai yang tidak diketahui tidak dianggap sama satu sama lain. Kita buktikan dengan dua pengguna baru yang sama-sama belum mencantumkan telepon:

sql
INSERT INTO pengguna (id, email, nama, telepon) VALUES
  (3, '[email protected]', 'Ani', NULL),
  (4, '[email protected]', 'Joko', NULL);

SELECT id, email, nama, telepon FROM pengguna ORDER BY id;

Output yang Dihasilkan:

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

+----+------------------+------+--------------+
| id | email            | nama | telepon      |
+----+------------------+------+--------------+
|  1 | [email protected] | Budi | 081211112222 |
|  2 | [email protected] | Siti | NULL         |
|  3 | [email protected]  | Ani  | NULL         |
|  4 | [email protected] | Joko | NULL         |
+----+------------------+------+--------------+
4 rows in set (0.00 sec)

Kedua baris diterima — kolom telepon kini memuat tiga nilai NULL (Siti, Ani, dan Joko) tanpa pelanggaran constraint. Bandingkan dengan kolom email: karena NOT NULL UNIQUE, kombinasi keduanya menolak kembar sekaligus menolak kosong. Pola NOT NULL UNIQUE dan UNIQUE saja dengan demikian menjawab dua kebutuhan berbeda.

UNIQUE Multi Kolom #

Keunikan sering hanya berlaku untuk kombinasi, bukan tiap kolom sendiri-sendiri: cabang toko boleh bernama Pusat di banyak kota, tetapi satu kota hanya boleh punya satu Pusat. Aturan seperti itu dinyatakan sebagai constraint tingkat tabel:

sql
CREATE TABLE cabang (
  id INT PRIMARY KEY,
  kota VARCHAR(50) NOT NULL,
  nama VARCHAR(50) NOT NULL,
  UNIQUE kota_nama (kota, nama)
);

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Bedahnya singkat namun penting:

  1. Baris 4 (UNIQUE kota_nama (kota, nama)): Constraint diberi nama kota_nama, lalu tanda kurung mencantumkan kolom yang gabungannya harus unik. Nilai kota boleh kembar dan nilai nama boleh kembar — yang dilarang hanya pasangan yang persis sama.
  2. Nama constraint: Nama kota_nama akan muncul di pesan galat, jadi beri nama yang deskriptif sejak awal.

Kita isi tiga cabang yang aman — perhatikan dua baris pertama sama-sama bernama Pusat di kota berbeda:

sql
INSERT INTO cabang (id, kota, nama) VALUES
  (1, 'Jakarta', 'Pusat'),
  (2, 'Bandung', 'Pusat'),
  (3, 'Surabaya', 'Timur');

SELECT id, kota, nama FROM cabang ORDER BY id;

Output yang Dihasilkan:

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

+----+----------+-------+
| id | kota     | nama  |
+----+----------+-------+
|  1 | Jakarta  | Pusat |
|  2 | Bandung  | Pusat |
|  3 | Surabaya | Timur |
+----+----------+-------+
3 rows in set (0.00 sec)

Duplikat kombinasi akan ditolak:

sql
-- @error
INSERT INTO cabang (id, kota, nama) VALUES (4, 'Jakarta', 'Pusat');

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

ERROR 1062 (23000): Duplicate entry 'Jakarta-Pusat' for key 'cabang.kota_nama'
Bye

Galat 1062 kembali muncul, dan nilainya ditampilkan sebagai pasangan: Duplicate entry 'Jakarta-Pusat' for key 'cabang.kota_nama' — format nilai1-nilai2 mencerminkan gabungan dua kolom yang dikunci. Sebaliknya, pasangan yang sebagian saja sama tetap sah:

sql
INSERT INTO cabang (id, kota, nama) VALUES (4, 'Jakarta', 'Selatan');

SELECT id, kota, nama FROM cabang ORDER BY id;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

+----+----------+---------+
| id | kota     | nama    |
+----+----------+---------+
|  1 | Jakarta  | Pusat   |
|  2 | Bandung  | Pusat   |
|  3 | Surabaya | Timur   |
|  4 | Jakarta  | Selatan |
+----+----------+---------+
4 rows in set (0.00 sec)

Baris keempat diterima karena Jakarta-Selatan belum pernah ada, walau kotanya sama dengan baris pertama. Prinsip “keunikan berlaku pada gabungan” ini identik dengan cara kerja composite primary key yang akan kita bahas di chapter khusus.

UNIQUE vs PRIMARY KEY #

Kedua constraint sama-sama menjaga keunikan dan sama-sama diimplementasikan lewat index, sehingga membingungkan bila disandingkan. Perbandingan berikut memisahkan perannya:

AspekPRIMARY KEYUNIQUE
Jumlah per tabelmaksimal satuboleh banyak
Nilai NULLdilarangdiperbolehkan (banyak baris)
Posisi di desainidentitas resmi barisaturan bisnis tambahan
Sifat indexclustered pada InnoDBindex biasa (secondary)
Penamaanselalu bernama PRIMARYdinamai atau otomatis

Kesimpulan praktisnya: setiap tabel punya satu primary key sebagai identitas, lalu UNIQUE dipasang pada kolom-kolom bisnis yang nilainya tidak boleh kembar — email akun, kode SKU barang, atau kombinasi kota dan nama cabang.

Referensi #

Keunikan kolom email dan pasangan kota-nama kini dijaga server secara otomatis. Namun constraint UNIQUE dan PRIMARY KEY sama-sama menolak nilai yang sudah ada — bagaimana dengan kolom yang nilainya memang belum tersedia? Perbedaan antara “belum ada nilainya” dan “nilainya kosong” dibedah tuntas oleh chapter NOT NULL Constraint. Sebelum itu, kita bereskan database latihan:

sql
DROP DATABASE IF EXISTS belajar_unik;

Output yang Dihasilkan:

text
Query OK, 2 rows affected (0.00 sec)

Dua tabel — pengguna dan cabang — ikut terbuang bersama constraint uniknya.