Dua daftar yang berbeda sering perlu dijadikan satu: “produk yang stoknya menipis” dan “produk yang harganya mahal” adalah dua pertanyaan berbeda, tetapi laporan stok gudang kadang ingin keduanya sekaligus — tanpa duplikat, atau justru dengan duplikat sebagai penanda. Alih-alih menjalankan dua query lalu menyalin-tempel hasilnya, SQL menyediakan empat operator set yang bekerja langsung pada keluaran SELECT: UNION, UNION ALL, INTERSECT, dan EXCEPT. Perilakunya mengikuti teori himpunan — dan, seperti biasa, ada aturan teknis yang harus dipatuhi.
Data Contoh #
Chapter ini hanya membutuhkan tabel produk dari domain toko:
CREATE TABLE produk (
id INTEGER PRIMARY KEY,
nama TEXT NOT NULL,
harga INTEGER NOT NULL,
stok INTEGER
);
INSERT INTO produk (id, nama, harga, stok) VALUES
(1, 'Kopi Gayo', 45000, 20), (2, 'Teh Melati', 18000, 35),
(3, 'Gula Aren', 25000, 15), (4, 'Sirup Vanila', 25000, NULL),
(5, 'Kopi Toraja', 52000, 10), (6, 'Teh Hijau', 22000, 0),
(7, 'Cangkir Keramik', 35000, 8);Kita akan memakai dua kumpulan yang sama berulang kali, jadi mari sepakati dulu isinya. Kumpulan A adalah produk dengan stok menipis (15 ke bawah) — perhatikan bahwa Sirup Vanila tidak masuk karena NULL <= 15 bernilai NULL, persis perilaku yang dibahas pada Chapter Operator Lanjutan: IN, BETWEEN, IS NULL. Kumpulan B adalah produk mahal (50000 ke atas):
SELECT nama FROM produk WHERE stok <= 15 ORDER BY nama;Output yang Dihasilkan:
Cangkir Keramik
Gula Aren
Kopi Toraja
Teh HijauSELECT nama FROM produk WHERE harga >= 50000 ORDER BY nama;Output yang Dihasilkan:
Kopi TorajaUNION dan UNION ALL #
UNION menggabungkan hasil kedua query lalu membuang baris duplikat:
SELECT nama FROM produk WHERE stok <= 15
UNION
SELECT nama FROM produk WHERE harga >= 50000
ORDER BY nama;Output yang Dihasilkan:
Cangkir Keramik
Gula Aren
Kopi Toraja
Teh HijauEmpat baris — Kopi Toraja hanya sekali meskipun memenuhi kedua syarat. UNION ALL mempertahankan duplikat apa adanya:
SELECT nama FROM produk WHERE stok <= 15
UNION ALL
SELECT nama FROM produk WHERE harga >= 50000
ORDER BY nama;Output yang Dihasilkan:
Cangkir Keramik
Gula Aren
Kopi Toraja
Kopi Toraja
Teh HijauKopi Toraja kini tampil dua kali — satu dari tiap sumber. Perbedaan ini bukan detail kosmetik: penghapusan duplikat menuntut engine SQLite melakukan langkah sortir dan pembandingan ekstra, sedangkan UNION ALL hanya menempelkan hasil. Untuk data besar di mana duplikat mustahil terjadi (misalnya rentang id yang tidak tumpang tindih), UNION ALL lebih cepat.
INTERSECT dan EXCEPT #
INTERSECT mempertahankan baris yang muncul di kedua sisi — irisan himpunan:
SELECT nama FROM produk WHERE stok <= 15
INTERSECT
SELECT nama FROM produk WHERE harga >= 50000;Output yang Dihasilkan:
Kopi TorajaEXCEPT mempertahankan baris sisi kiri yang tidak ada di sisi kanan — selisih himpunan. Urutan sisi penting, dan ini bukan operasi komutatif:
SELECT nama FROM produk WHERE stok <= 15
EXCEPT
SELECT nama FROM produk WHERE harga >= 50000
ORDER BY nama;Output yang Dihasilkan:
Cangkir Keramik
Gula Aren
Teh HijauDibalik posisinya, EXCEPT akan mengembalikan himpunan B dikurangi A — dalam data kita hasilnya kosong karena B hanya berisi Kopi Toraja yang juga anggota A.
Aturan Kolom: Jumlah dan Kompatibilitas #
Kedua sisi operator set harus menghasilkan jumlah kolom yang sama, dan tipe data tiap kolom harus sebanding. Melanggarnya langsung ditolak pada tahap persiapan query:
sqlite3 toko.db "SELECT nama FROM produk WHERE stok <= 15 EXCEPT SELECT id, nama FROM produk WHERE harga >= 50000;"Output yang Dihasilkan:
Error: in prepare, SELECTs to the left and right of EXCEPT do not have the same number of result columnsNama kolom hasil mengikuti sisi kiri. Sisi kanan boleh memberi nama kolom berbeda, tetapi nama itu dibuang.
ORDER BY Hanya di Ujung #
Satu ORDER BY boleh ditulis dan hanya boleh ditulis di akhir keseluruhan query — memformat gabungan akhir, bukan per sisi:
SELECT nama FROM produk WHERE stok <= 15
UNION
SELECT nama FROM produk WHERE harga >= 50000
ORDER BY nama DESC;Output yang Dihasilkan:
Teh Hijau
Kopi Toraja
Gula Aren
Cangkir KeramikMenaruh ORDER BY di tengah (sebelum UNION) akan ditolak dengan galat ORDER BY clause should come after UNION not before, karena parser membaca keseluruhan gabungan sebagai satu statement dengan satu hasil akhir.
| Operator | Baris hasil | Duplikat |
|---|---|---|
UNION | Semua baris kedua sisi | Dihapus |
UNION ALL | Semua baris kedua sisi | Dipertahankan |
INTERSECT | Hanya yang ada di kedua sisi | Dihapus |
EXCEPT | Sisi kiri tanpa isi sisi kanan | Dihapus |
Penyusunan hasil terakhir yang belum kita bahas adalah logika kondisional di dalam satu query — “jika harganya segini maka labelnya begitu” — yang diselesaikan oleh ekspresi CASE pada chapter penutup section ini.