Tabel transaksi seperti pesanan menyimpan satu baris per kejadian — dan pembeli yang sama muncul berkali-kali. Begitu kita hanya butuh daftar “kota mana saja yang pernah menerima kiriman” atau “siapa saja pelanggan kita”, baris kembar itu berubah menjadi kebisingan yang menipu: hitungan baris melebih-lebihkan, dropdown di aplikasi menampilkan opsi ganda. Klausa DISTINCT membersihkan hasil query dari baris yang duplikat, dan PostgreSQL menambahkan satu senjata yang tidak dimiliki kebanyakan database lain: DISTINCT ON, yang memilih satu baris wakil terbaik per kelompok — misalnya pesanan terakhir setiap pelanggan — dalam satu perintah singkat.

Menyiapkan Tabel Latihan #

Kita pakai tabel pesanan berisi delapan transaksi dari tiga pelanggan di empat kota. Kolom tanggal dan total akan berguna saat kita sampai di DISTINCT ON:

sql buat-tabel.sql
CREATE TABLE pesanan (
    id        integer PRIMARY KEY,
    pelanggan text NOT NULL,
    kota      text NOT NULL,
    total     numeric(10, 2) NOT NULL,
    tanggal   date NOT NULL
);

INSERT INTO pesanan (id, pelanggan, kota, total, tanggal) VALUES
    (1, 'Budi', 'Jakarta',   45000, DATE '2026-01-10'),
    (2, 'Siti', 'Bandung',   30000, DATE '2026-01-12'),
    (3, 'Budi', 'Jakarta',   12000, DATE '2026-02-01'),
    (4, 'Siti', 'Surabaya',  30000, DATE '2026-01-20'),
    (5, 'Budi', 'Jakarta',   25000, DATE '2026-02-15'),
    (6, 'Dewi', 'Jakarta',   30000, DATE '2026-01-25'),
    (7, 'Siti', 'Bandung',   45000, DATE '2026-02-03'),
    (8, 'Dewi', 'Medan',     15000, DATE '2026-02-08');

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 8

Delapan pesanan hanya berasal dari tiga nama pelanggan (Budi, Siti, Dewi) dan empat kota — duplikasi inilah bahan utama chapter ini.

DISTINCT pada Satu Kolom #

Kata kunci DISTINCT diletakkan tepat setelah SELECT dan sebelum daftar kolom. PostgreSQL membandingkan setiap baris hasil dengan baris lainnya, lalu membuang yang isinya sama persis:

-- Tanpa DISTINCT: delapan baris, kota yang sama tampil berulang.
SELECT kota
FROM pesanan
ORDER BY kota, id;

-- Dengan DISTINCT: satu baris per nilai kota yang berbeda.
SELECT DISTINCT kota
FROM pesanan
ORDER BY kota;

-- Pelanggan unik dari sisi nama — tiga pembeli tetap dari delapan pesanan.
SELECT DISTINCT pelanggan
FROM pesanan
ORDER BY pelanggan;

Output yang Dihasilkan:

text
   kota   
----------
 Bandung
 Bandung
 Jakarta
 Jakarta
 Jakarta
 Jakarta
 Medan
 Surabaya
(8 rows)

   kota   
----------
 Bandung
 Jakarta
 Medan
 Surabaya
(4 rows)

 pelanggan 
-----------
 Budi
 Dewi
 Siti
(3 rows)

Query pertama memperlihatkan masalahnya: Jakarta tampil empat kali dan Bandung dua kali dari delapan baris. Query kedua memangkasnya menjadi empat kota, dan query ketiga menunjukkan tiga pelanggan unik. Sama seperti biasa course ini, ORDER BY tetap dipasang pada semua query DISTINCT — hasil deduplikasi tanpa ORDER BY tidak menjamin urutan barisnya.

DISTINCT pada Beberapa Kolom: Unik per Baris Utuh #

Kesalahpahaman paling umum tentang DISTINCT adalah mengira kata ini hanya menempel pada kolom pertama. Sebenarnya DISTINCT berlaku pada seluruh baris hasil: dua baris dianggap duplikat hanya jika semua kolom yang dipilih bernilai sama:

-- DISTINCT dengan dua kolom: baris dianggap duplikat hanya jika
-- SELURUH pasangan nilainya sama.
SELECT DISTINCT pelanggan, kota
FROM pesanan
ORDER BY pelanggan, kota;

-- Bandingkan: nilai unik kolom pelanggan saja ada 3, kolom kota saja ada 4,
-- tetapi kombinasi (pelanggan, kota) menghasilkan 5 pasangan.
SELECT DISTINCT pelanggan
FROM pesanan
ORDER BY pelanggan;

SELECT DISTINCT kota
FROM pesanan
ORDER BY kota;

Output yang Dihasilkan:

text
 pelanggan |   kota   
-----------+----------
 Budi      | Jakarta
 Dewi      | Jakarta
 Dewi      | Medan
 Siti      | Bandung
 Siti      | Surabaya
(5 rows)

 pelanggan 
-----------
 Budi
 Dewi
 Siti
(3 rows)

   kota   
----------
 Bandung
 Jakarta
 Medan
 Surabaya
(4 rows)

Pasangan (pelanggan, kota) menghasilkan lima baris: Siti muncul dua kali karena belanja di dua kota berbeda (Bandung dan Surabaya), dan Dewi juga dua kali (Jakarta dan Medan). Dua pesanan Siti di Bandung (id 2 dan 7) hanya menyumbang satu baris karena pasangan nilainya identik. Bandingkan tiga angka pada output: 3 pelanggan unik, 4 kota unik, 5 kombinasi unik — masing-masing menjawab pertanyaan yang berbeda, dan DISTINCT menuntut Anda sadar pertanyaan mana yang sedang diajukan.

DISTINCT ON: Satu Baris Terbaik per Kelompok #

Inilah fitur khas PostgreSQL yang menjelaskan judulnya: DISTINCT ON (kolom) mempertahankan satu baris per nilai kolom tersebut — dan baris mana yang dipilih ditentukan oleh ORDER BY. Baris pertama menurut urutan itulah yang menjadi wakil kelompoknya:

sql distinct-on.sql
-- DISTINCT ON (pelanggan): pertahankan SATU baris per pelanggan —
-- baris pertama menurut urutan ORDER BY (tanggal terbaru, lalu id terbesar
-- sebagai pemecah seri). Ini pesanan TERAKHIR setiap pelanggan.
SELECT DISTINCT ON (pelanggan)
       id, pelanggan, tanggal, total
FROM pesanan
ORDER BY pelanggan, tanggal DESC, id DESC;

-- Balik urutan tanggalnya: kini kita dapat pesanan PERTAMA setiap pelanggan.
SELECT DISTINCT ON (pelanggan)
       id, pelanggan, tanggal, total
FROM pesanan
ORDER BY pelanggan, tanggal ASC, id ASC;

-- DISTINCT ON juga bekerja untuk pertanyaan lain: pesanan TERBESAR per kota.
SELECT DISTINCT ON (kota)
       id, kota, pelanggan, total
FROM pesanan
ORDER BY kota, total DESC, id DESC;

Output yang Dihasilkan:

text
 id | pelanggan |  tanggal   |  total   
----+-----------+------------+----------
  5 | Budi      | 2026-02-15 | 25000.00
  8 | Dewi      | 2026-02-08 | 15000.00
  7 | Siti      | 2026-02-03 | 45000.00
(3 rows)

 id | pelanggan |  tanggal   |  total   
----+-----------+------------+----------
  1 | Budi      | 2026-01-10 | 45000.00
  6 | Dewi      | 2026-01-25 | 30000.00
  2 | Siti      | 2026-01-12 | 30000.00
(3 rows)

 id |   kota   | pelanggan |  total   
----+----------+-----------+----------
  7 | Bandung  | Siti      | 45000.00
  1 | Jakarta  | Budi      | 45000.00
  8 | Medan    | Dewi      | 15000.00
  4 | Surabaya | Siti      | 30000.00
(4 rows)

Mari kita bedah mekanismenya pada query pertama:

  1. DISTINCT ON (pelanggan) — PostgreSQL mengelompokkan hasil berdasar kolom pelanggan dan berjanji mengembalikan satu baris per kelompok.
  2. ORDER BY pelanggan, tanggal DESC, id DESC — urutan pengurutan menentukan wakil tiap kelompok: baris pertama dari kelompok itu. pelanggan wajib menjadi kolom pertama (aturannya dibahas sebentar lagi), tanggal DESC memilih pesanan terbaru, dan id DESC menjadi pemecah seri bila satu pelanggan punya dua pesanan di tanggal yang sama — kebiasaan determinisme penuh yang dipakai konsisten di course ini.
  3. Kolom id, tanggal, total ikut ditampilkan dari baris terpilih — inilah yang tidak bisa dicapai DISTINCT biasa maupun GROUP BY sederhana.

Hasilnya: pesanan terakhir Budi adalah id 5 (15 Februari 2026), pesanan pertama Budi kembali ke id 1 (10 Januari 2026), dan versi “pesanan terbesar per kota” menobatkan pesanan 45000 milik Siti di Bandung. Query ketiga memperlihatkan bahwa DISTINCT ON tidak terbatas pada “terbaru”: kriteria pemilihan wakil sepenuhnya ditentukan kombinasi ORDER BY.

Aturan ORDER BY pada DISTINCT ON #

DISTINCT ON memasang satu syarat khusus: ekspresi di dalam DISTINCT ON (...) harus menjadi kolom-kolom awal klausa ORDER BY. Logikanya masuk akal — server harus mengurutkan per kelompok dulu sebelum bisa memilih baris pertama tiap kelompok. Melanggarnya memicu error:

-- Aturan DISTINCT ON: ekspresi di DISTINCT ON harus menjadi
-- KOLOM-KOLOM AWAL klausa ORDER BY. Query ini melanggar aturan itu
-- karena ORDER BY dimulai dari tanggal, bukan pelanggan.
SELECT DISTINCT ON (pelanggan)
       id, pelanggan, tanggal, total
FROM pesanan
ORDER BY tanggal, id;

Output yang Dihasilkan:

text
psql:distinct-on-error.sql:7: ERROR:  SELECT DISTINCT ON expressions must match initial ORDER BY expressions
LINE 1: SELECT DISTINCT ON (pelanggan)
                            ^

Error SELECT DISTINCT ON expressions must match initial ORDER BY expressions menandai pelanggarannya. Perbaikannya selalu satu pola: mulai ORDER BY dengan ekspresi yang sama seperti di DISTINCT ON (...), baru tambahkan kolom pemilih wakil setelahnya — persis seperti tiga contoh DISTINCT ON yang sudah berjalan di atas.

DISTINCT vs GROUP BY #

GROUP BY — yang akan dibahas tuntas di Section Agregasi dan Grouping — juga mampu memangkas hasil menjadi satu baris per kelompok. Tanpa function agregat apa pun, hasilnya identik dengan DISTINCT:

-- DISTINCT: "tampilkan nilai pelanggan yang berbeda".
SELECT DISTINCT pelanggan
FROM pesanan
ORDER BY pelanggan;

-- GROUP BY tanpa agregat menghasilkan hasil yang identik:
-- satu baris per kelompok pelanggan.
SELECT pelanggan
FROM pesanan
GROUP BY pelanggan
ORDER BY pelanggan;

Output yang Dihasilkan:

text
 pelanggan 
-----------
 Budi
 Dewi
 Siti
(3 rows)

 pelanggan 
-----------
 Budi
 Dewi
 Siti
(3 rows)

Kedua query mengembalikan tiga baris yang sama. Pilih DISTINCT saat maksudnya memang “buang duplikat tampilan”; pilih GROUP BY saat setiap kelompok perlu dihitung sesuatu (count, sum, rata-rata). Menariknya, DISTINCT ON menutup celah di antara keduanya: deduplikasi ala DISTINCT tetapi dengan kolom pendukung dari baris wakil terpilih — sesuatu yang di database lain harus dikerjakan dengan subquery atau window function.

Hasil bersih tanpa duplikat hanyalah setengah dari laporan yang enak dibaca; setengah lainnya adalah urutan penyajian. Baris-baris Budi, Dewi, Siti di atas kebetulan keluar alfabetis hanya karena ORDER BY kita — chapter berikutnya membahas ORDER BY secara penuh: arah sort, banyak kolom, perlakuan NULL, dan kebiasaan tie-break yang membuat hasil selalu bisa diprediksi.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

buat-tabel.sql

Unduh berkas
sql
CREATE TABLE pesanan (
    id        integer PRIMARY KEY,
    pelanggan text NOT NULL,
    kota      text NOT NULL,
    total     numeric(10, 2) NOT NULL,
    tanggal   date NOT NULL
);

INSERT INTO pesanan (id, pelanggan, kota, total, tanggal) VALUES
    (1, 'Budi', 'Jakarta',   45000, DATE '2026-01-10'),
    (2, 'Siti', 'Bandung',   30000, DATE '2026-01-12'),
    (3, 'Budi', 'Jakarta',   12000, DATE '2026-02-01'),
    (4, 'Siti', 'Surabaya',  30000, DATE '2026-01-20'),
    (5, 'Budi', 'Jakarta',   25000, DATE '2026-02-15'),
    (6, 'Dewi', 'Jakarta',   30000, DATE '2026-01-25'),
    (7, 'Siti', 'Bandung',   45000, DATE '2026-02-03'),
    (8, 'Dewi', 'Medan',     15000, DATE '2026-02-08');

distinct-multi-kolom.sql

Unduh berkas
sql
-- DISTINCT dengan dua kolom: baris dianggap duplikat hanya jika
-- SELURUH pasangan nilainya sama.
SELECT DISTINCT pelanggan, kota
FROM pesanan
ORDER BY pelanggan, kota;

-- Bandingkan: nilai unik kolom pelanggan saja ada 3, kolom kota saja ada 4,
-- tetapi kombinasi (pelanggan, kota) menghasilkan 5 pasangan.
SELECT DISTINCT pelanggan
FROM pesanan
ORDER BY pelanggan;

SELECT DISTINCT kota
FROM pesanan
ORDER BY kota;

distinct-on-error.sql

Unduh berkas
sql
-- Aturan DISTINCT ON: ekspresi di DISTINCT ON harus menjadi
-- KOLOM-KOLOM AWAL klausa ORDER BY. Query ini melanggar aturan itu
-- karena ORDER BY dimulai dari tanggal, bukan pelanggan.
SELECT DISTINCT ON (pelanggan)
       id, pelanggan, tanggal, total
FROM pesanan
ORDER BY tanggal, id;

distinct-on.sql

Unduh berkas
sql
-- DISTINCT ON (pelanggan): pertahankan SATU baris per pelanggan —
-- baris pertama menurut urutan ORDER BY (tanggal terbaru, lalu id terbesar
-- sebagai pemecah seri). Ini pesanan TERAKHIR setiap pelanggan.
SELECT DISTINCT ON (pelanggan)
       id, pelanggan, tanggal, total
FROM pesanan
ORDER BY pelanggan, tanggal DESC, id DESC;

-- Balik urutan tanggalnya: kini kita dapat pesanan PERTAMA setiap pelanggan.
SELECT DISTINCT ON (pelanggan)
       id, pelanggan, tanggal, total
FROM pesanan
ORDER BY pelanggan, tanggal ASC, id ASC;

-- DISTINCT ON juga bekerja untuk pertanyaan lain: pesanan TERBESAR per kota.
SELECT DISTINCT ON (kota)
       id, kota, pelanggan, total
FROM pesanan
ORDER BY kota, total DESC, id DESC;

distinct-satu-kolom.sql

Unduh berkas
sql
-- Tanpa DISTINCT: delapan baris, kota yang sama tampil berulang.
SELECT kota
FROM pesanan
ORDER BY kota, id;

-- Dengan DISTINCT: satu baris per nilai kota yang berbeda.
SELECT DISTINCT kota
FROM pesanan
ORDER BY kota;

-- Pelanggan unik dari sisi nama — tiga pembeli tetap dari delapan pesanan.
SELECT DISTINCT pelanggan
FROM pesanan
ORDER BY pelanggan;

distinct-vs-group-by.sql

Unduh berkas
sql
-- DISTINCT: "tampilkan nilai pelanggan yang berbeda".
SELECT DISTINCT pelanggan
FROM pesanan
ORDER BY pelanggan;

-- GROUP BY tanpa agregat menghasilkan hasil yang identik:
-- satu baris per kelompok pelanggan.
SELECT pelanggan
FROM pesanan
GROUP BY pelanggan
ORDER BY pelanggan;