Chapter sebelumnya membuktikan bahwa UNION melebur baris kembar menjadi satu — perilaku yang diinginkan saat menyusun daftar member tanpa ganda. Namun penghapusan duplikat tersebut bukan pekerjaan gratis: MySQL harus membandingkan setiap baris hasil sebelum boleh mengembalikannya, dan biaya itu membesar seiring jumlah baris. Sering pula duplikat justru wajar adanya, misalnya dua pesanan berisi produk yang sama di hari yang sama. Untuk kedua situasi itu MySQL menyediakan UNION ALL: semua baris dari semua sumber ditumpuk apa adanya tanpa pemeriksaan duplikat sama sekali. Pada chapter ini kita membandingkan perilaku UNION dan UNION ALL pada data yang sama, lalu membangun laporan kuartalan dengan penanda asal berupa kolom literal 'Q1' dan 'Q2', dan menutupnya dengan rekap COUNT per sumber.
Menyiapkan Data Dua Kuartal #
Kita pakai database latihan belajar_set kembali dengan dua tabel sumber: pesanan_q1 untuk kuartal pertama dan pesanan_q2 untuk kuartal kedua. Kedua tabel berbagi struktur yang sama — tanpa primary key, karena latihan ini memang ingin menampung baris yang boleh kembar:
DROP DATABASE IF EXISTS belajar_set;
CREATE DATABASE belajar_set;
USE belajar_set;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE pesanan_q1 (
tanggal DATE NOT NULL,
pelanggan VARCHAR(50) NOT NULL,
produk VARCHAR(50) NOT NULL,
jumlah INT NOT NULL
);
INSERT INTO pesanan_q1 (tanggal, pelanggan, produk, jumlah) VALUES
('2026-01-05', 'Budi', 'Kopi Gayo', 2),
('2026-01-05', 'Siti', 'Teh Melati', 3),
('2026-02-10', 'Rina', 'Kopi Toraja', 1),
('2026-02-10', 'Rina', 'Kopi Toraja', 1),
('2026-03-15', 'Dewi', 'Gula Aren', 4);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 5 rows affected (0.00 sec)
Records: 5 Duplicates: 0 Warnings: 0CREATE TABLE pesanan_q2 (
tanggal DATE NOT NULL,
pelanggan VARCHAR(50) NOT NULL,
produk VARCHAR(50) NOT NULL,
jumlah INT NOT NULL
);
INSERT INTO pesanan_q2 (tanggal, pelanggan, produk, jumlah) VALUES
('2026-04-02', 'Andi', 'Kopi Gayo', 2),
('2026-05-20', 'Budi', 'Teh Melati', 3),
('2026-05-20', 'Siti', 'Kopi Toraja', 5),
('2026-06-30', 'Tono', 'Gula Aren', 6);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0Tabel pesanan_q1 sengaja memuat dua baris Rina yang identik pada 2026-02-10 — dua transaksi terpisah dengan produk dan jumlah yang sama. Seluruh tanggal ditulis sebagai literal tetap seperti '2026-01-05' supaya setiap contoh di chapter ini deterministik. Karena rentang kuartal pertama dan kuartal kedua tidak mungkin berbagi tanggal, kedua sumber ini juga tidak memiliki baris kembar lintas sumber — hanya duplikat di dalam pesanan_q1 saja.
UNION ALL Mempertahankan Semua Baris #
Mari kita gabungkan kedua tabel dengan UNION ALL dan hitung barisnya:
SELECT tanggal, pelanggan, produk, jumlah FROM pesanan_q1
UNION ALL
SELECT tanggal, pelanggan, produk, jumlah FROM pesanan_q2
ORDER BY tanggal, pelanggan, produk;Output yang Dihasilkan:
+------------+-----------+-------------+--------+
| tanggal | pelanggan | produk | jumlah |
+------------+-----------+-------------+--------+
| 2026-01-05 | Budi | Kopi Gayo | 2 |
| 2026-01-05 | Siti | Teh Melati | 3 |
| 2026-02-10 | Rina | Kopi Toraja | 1 |
| 2026-02-10 | Rina | Kopi Toraja | 1 |
| 2026-03-15 | Dewi | Gula Aren | 4 |
| 2026-04-02 | Andi | Kopi Gayo | 2 |
| 2026-05-20 | Budi | Teh Melati | 3 |
| 2026-05-20 | Siti | Kopi Toraja | 5 |
| 2026-06-30 | Tono | Gula Aren | 6 |
+------------+-----------+-------------+--------+
9 rows in set (0.00 sec)Sembilan baris tampil dalam hasil: lima baris dari pesanan_q1 dan empat baris dari pesanan_q2, jumlah yang persis sama dengan total baris kedua tabel. Dua baris Rina tanggal 2026-02-10 tetap utuh berdampingan karena UNION ALL tidak melakukan pemeriksaan duplikat — baris ditumpuk langsung begitu query tiap sumber selesai. Operator ini juga lebih cepat pada data besar: tanpa tahap pembandingan baris, MySQL cukup mengalirkan hasil kedua query ke satu kumpulan output.
Sebagai pembanding, jalankan UNION biasa pada data yang persis sama:
SELECT tanggal, pelanggan, produk, jumlah FROM pesanan_q1
UNION
SELECT tanggal, pelanggan, produk, jumlah FROM pesanan_q2
ORDER BY tanggal, pelanggan, produk;Output yang Dihasilkan:
+------------+-----------+-------------+--------+
| tanggal | pelanggan | produk | jumlah |
+------------+-----------+-------------+--------+
| 2026-01-05 | Budi | Kopi Gayo | 2 |
| 2026-01-05 | Siti | Teh Melati | 3 |
| 2026-02-10 | Rina | Kopi Toraja | 1 |
| 2026-03-15 | Dewi | Gula Aren | 4 |
| 2026-04-02 | Andi | Kopi Gayo | 2 |
| 2026-05-20 | Budi | Teh Melati | 3 |
| 2026-05-20 | Siti | Kopi Toraja | 5 |
| 2026-06-30 | Tono | Gula Aren | 6 |
+------------+-----------+-------------+--------+
8 rows in set (0.00 sec)Hasilnya tinggal delapan baris: kedua baris Rina dilebur menjadi satu. Perlu dicatat bahwa penghapusan duplikat UNION berlaku untuk seluruh hasil gabungan, termasuk baris kembar yang berasal dari sumber yang sama — bukan hanya baris yang tumpang tindih antar sumber. Ringkasan perbedaan keduanya sebagai berikut:
| Aspek | UNION | UNION ALL |
|---|---|---|
| Baris duplikat | dihapus (seluruh baris dibandingkan) | dipertahankan semuanya |
| Jumlah baris hasil pada data kita | 8 | 9 |
| Kerja server | ada tahap dedup tambahan | langsung menumpuk hasil |
| Kapan dipilih | daftar harus unik (mis. daftar pelanggan) | sumber dijamin tidak tumpang tindih, atau duplikat memang wajar |
Penanda Asal dengan Kolom Literal #
Gabungan UNION ALL membuat baris dari dua sumber bercampur, dan sering kita perlu tahu asal tiap baris. Triknya adalah menambahkan kolom literal di posisi pertama pada kedua query: string 'Q1' pada query pertama dan 'Q2' pada query kedua:
SELECT 'Q1' AS kuartal, tanggal, pelanggan, produk, jumlah FROM pesanan_q1
UNION ALL
SELECT 'Q2', tanggal, pelanggan, produk, jumlah FROM pesanan_q2
ORDER BY kuartal, tanggal, pelanggan;Output yang Dihasilkan:
+---------+------------+-----------+-------------+--------+
| kuartal | tanggal | pelanggan | produk | jumlah |
+---------+------------+-----------+-------------+--------+
| Q1 | 2026-01-05 | Budi | Kopi Gayo | 2 |
| Q1 | 2026-01-05 | Siti | Teh Melati | 3 |
| Q1 | 2026-02-10 | Rina | Kopi Toraja | 1 |
| Q1 | 2026-02-10 | Rina | Kopi Toraja | 1 |
| Q1 | 2026-03-15 | Dewi | Gula Aren | 4 |
| Q2 | 2026-04-02 | Andi | Kopi Gayo | 2 |
| Q2 | 2026-05-20 | Budi | Teh Melati | 3 |
| Q2 | 2026-05-20 | Siti | Kopi Toraja | 5 |
| Q2 | 2026-06-30 | Tono | Gula Aren | 6 |
+---------+------------+-----------+-------------+--------+
9 rows in set (0.00 sec)Mari kita bedah baris per baris: literal 'Q1' di query pertama bukan kolom dari tabel, melainkan teks tetap yang MySQL isikan ke setiap baris hasil dari sumber tersebut, sehingga lima baris pertama semuanya membawa label Q1. Literal 'Q2' di query kedua bekerja serupa untuk empat baris berikutnya, dan kali ini penulisan AS boleh dihilangkan karena nama kolom hasil memang diambil dari query pertama yang telah menamainya kuartal melalui AS. Kolom penanda ini kemudian bisa dipakai ORDER BY kuartal, tanggal, pelanggan — pengurutan mengelompokkan kuartal pertama lebih dulu baru kuartal kedua. Pola yang sama berlaku untuk sumber lain: 'Jakarta'/'Bandung' untuk dua cabang toko, atau nama file untuk data gabungan hasil impor.
Rekap Jumlah per Sumber dengan GROUP BY #
Kolom penanda menjadi jauh lebih berguna begitu digabungkan dengan agregasi. Pertanyaan “berapa banyak pesanan dan total unit tiap kuartal?” terjawab dengan membungkus gabungan sebagai derived table, lalu mengelompokkan berdasarkan kolom kuartal:
SELECT kuartal, COUNT(*) AS jumlah_pesanan, SUM(jumlah) AS total_unit
FROM (
SELECT 'Q1' AS kuartal, jumlah FROM pesanan_q1
UNION ALL
SELECT 'Q2', jumlah FROM pesanan_q2
) AS gabungan
GROUP BY kuartal
ORDER BY kuartal;Output yang Dihasilkan:
+---------+----------------+------------+
| kuartal | jumlah_pesanan | total_unit |
+---------+----------------+------------+
| Q1 | 5 | 11 |
| Q2 | 4 | 16 |
+---------+----------------+------------+
2 rows in set (0.00 sec)Derived table bernama gabungan merangkum hasil UNION ALL menjadi sumber data biasa yang bisa diolah GROUP BY. Kuartal pertama menghasilkan jumlah_pesanan sebanyak 5 dengan total_unit 11, sedangkan kuartal kedua mencatat 4 pesanan dengan 16 unit. Perhatikan bahwa kedua baris Rina ikut terhitung di kuartal pertama — konsekuensi langsung dari UNION ALL yang mempertahankan duplikat. Bentuk penulisan ini sebenarnya masih terasa berbelit: subquery di klausa FROM harus dibungkus tanda kurung dan diberi alias. Chapter berikutnya memperkenalkan WITH — cara memberi nama sementara pada sebuah query supaya ungkapan bertingkat seperti ini terbaca dari atas ke bawah seperti daftar langkah.
Membersihkan Database Latihan #
Rangkaian latihan selesai dan database latihan kita jatuhkan kembali:
DROP DATABASE belajar_set;Output yang Dihasilkan:
Query OK, 2 rows affected (0.00 sec)Referensi #
Kedua operator himpunan kini tergambar tegas: UNION membersihkan duplikat dengan biaya pembandingan baris, sedangkan UNION ALL menumpuk apa adanya dan lebih gesit. Keduanya menyelesaikan masalah penggabungan baris, tetapi gabungan seperti (... UNION ALL ...) GROUP BY tadi mulai menunjukkan betapa cepatnya query bertumpuk menjadi sulit dibaca. Saatnya berkenalan dengan Common Table Expression — fitur WITH yang memberi nama pada subquery supaya query panjang terbaca bertahap.