Join yang benar dimulai sebelum query ditulis, yaitu membaca model data untuk
memahami tabel mana yang memuat apa dan kunci mana yang menghubungkannya.
Topik ini menambahkan konteks model data pada database latihan analitik,
memperlihatkan pola bintang yang melandasinya, dan menjalankan join serta
anti-join pada skema tersebut. Setelah membacanya, query lintas tabel ditulis
dengan keyakinan atas granularitas hasilnya, bukan dengan percobaan sampai
angkanya terlihat masuk akal.
Membaca model data sebelum menulis query#
Model data menjawab tiga pertanyaan, yaitu tabel apa yang ada, apa yang
diwakili satu barisnya, dan kunci mana yang menghubungkannya. Pada basis data
produksi, pertanyaan itu dijawab dari katalognya, misalnya perintah
\d nama_tabel pada psql yang menampilkan kolom, tipe, dan kunci asingnya,
serta percakapan singkat dengan pemilik datanya.
Kunci primer (primary key) mengidentifikasi satu baris secara unik, dan
kunci asing (foreign key) menunjuk ke kunci primer tabel lain. Pada
database latihan ini, dim_stasiun.id_stasiun adalah kunci primer, sedangkan
fakta_ukur.id_stasiun adalah kunci asingnya, dan pasangan inilah yang
menjadi landasan setiap join.
Granularitas ditanyakan dengan kalimat yang sama seperti pada modul jenis dan bentuk data, yaitu satu baris fakta mewakili satu pengukuran pada satu jam untuk satu stasiun. Kalimat itu yang menentukan bahwa agregasi per stasiun sah, sedangkan penjumlahan antar jam perlu dipikirkan maknanya.
Pola bintang yang melandasinya#
Pola yang mendasari banyak gudang data adalah star schema, yaitu satu tabel fakta yang berada di tengah dengan tabel dimensi yang mengelilinginya, masing-masing terhubung lewat kunci asingnya. Diagram berikut memetakan skema database latihan beserta perluasan wajarnya pada praktik.
Keunggulan pola ini adalah pemisahan yang jelas, yaitu tabel fakta yang tumbuh besar hanya menyimpan angka dan kunci, sedangkan nama panjang dan metadata tinggal di dimensi yang kecil. Query agregasinya pun seragam, yaitu angka dari fakta dipotong menurut kolom dari dimensi, dan pemangkas yang sama berlaku dari data pengukuran sampai data transaksi.
Baris Stasiun C pada dimensi yang belum memiliki pengukuran bukan
keanehan, melainkan keadaan normal pada model ini, dan bagian anti-join di
bawah memanfaatkannya sebagai latihan.
Join dan granularitas hasil#
Inner join memasangkan baris yang kuncinya bertemu di kedua sisi, dan hasil join satu ke banyak tetap bergranularitas fakta, yaitu satu baris pengukuran dengan kolom metadata yang menempel. Aturan yang menyertai adalah jumlah baris hasil dapat diperiksa, dan pada fakta yang granularitasnya jam, penggandaan terjadi bila kunci di sisi dimensi tidak unik.
# siapkan ulang database latihan agar topik dapat dijalankan sendiri
psql -h /home/kal/my-project/pgsock -U postgres -c "DROP DATABASE IF EXISTS analitik"
psql -h /home/kal/my-project/pgsock -U postgres -c "CREATE DATABASE analitik"
psql -h /home/kal/my-project/pgsock -U postgres -d analitik -c "CREATE TABLE dim_stasiun (id_stasiun serial PRIMARY KEY, nama text NOT NULL, wilayah text NOT NULL)"
psql -h /home/kal/my-project/pgsock -U postgres -d analitik -c "INSERT INTO dim_stasiun (nama, wilayah) VALUES ('Stasiun A','Kawasan Sekolah'), ('Stasiun B','Jalan Utama'), ('Stasiun C','Taman Kota')"
psql -h /home/kal/my-project/pgsock -U postgres -d analitik -c "CREATE TABLE fakta_ukur (id serial PRIMARY KEY, waktu timestamp NOT NULL, id_stasiun integer REFERENCES dim_stasiun, pm25 numeric NOT NULL, suhu_c numeric, kelembapan numeric)"
psql -h /home/kal/my-project/pgsock -U postgres -d analitik -c "INSERT INTO fakta_ukur (waktu, id_stasiun, pm25, suhu_c, kelembapan) VALUES ('2026-03-01 07:00',1,38.2,25.6,71.0), ('2026-03-01 08:00',1,44.9,26.8,69.4), ('2026-03-01 09:00',1,41.3,27.9,68.7), ('2026-03-01 07:00',2,58.7,24.9,72.3), ('2026-03-01 08:00',2,66.1,25.8,70.9), ('2026-03-01 09:00',2,61.4,26.7,71.5)"DROP DATABASE
CREATE DATABASE
CREATE TABLE
INSERT 0 3
CREATE TABLE
INSERT 0 6# inner join: pengukuran dengan nama stasiunnya
psql -h /home/kal/my-project/pgsock -U postgres -d analitik -c "SELECT f.waktu, s.nama, f.pm25 FROM fakta_ukur f JOIN dim_stasiun s ON f.id_stasiun = s.id_stasiun ORDER BY f.waktu, s.nama LIMIT 4" waktu | nama | pm25
---------------------+-----------+------
2026-03-01 07:00:00 | Stasiun A | 38.2
2026-03-01 07:00:00 | Stasiun B | 58.7
2026-03-01 08:00:00 | Stasiun A | 44.9
2026-03-01 08:00:00 | Stasiun B | 66.1
(4 rows)Alias satu huruf pada kedua tabel menjaga query tetap terbaca, dan urutan
kolom hasilnya ditentukan SELECT, bukan urutan tabel pada FROM. Agregasi di
atas hasil join pun aman dari penggandaan karena id_stasiun unik pada
dimensinya, dan pemeriksaan keunikannya dapat ditegaskan dengan constraint
UNIQUE.
Anti-join: menemukan yang tidak berpasangan#
LEFT JOIN mempertahankan seluruh baris sisi kiri dan mengisi kosong dengan NULL untuk baris yang tidak bertemu pasangannya, dan agregasi count atas kolom sisi kanan menghasilkan daftar dimensi yang tidak punya fakta, yang disebut anti-join.
# anti-join: dimensi tanpa pengukuran
psql -h /home/kal/my-project/pgsock -U postgres -d analitik -c "SELECT s.nama, count(f.id) AS jumlah_fakta FROM dim_stasiun s LEFT JOIN fakta_ukur f ON f.id_stasiun = s.id_stasiun GROUP BY s.nama ORDER BY s.nama" nama | jumlah_fakta
-----------+--------------
Stasiun A | 3
Stasiun B | 3
Stasiun C | 0
(3 rows)Stasiun C muncul dengan nol fakta, dan pola count(f.id) yang menghitung
kolom tabel kanan adalah pembedanya, karena count(*) akan menghitung baris
kosong itu sebagai satu. Anti-join semacam ini adalah pemeriksaan rutin
integritas antar sumber, sejalan dengan audit baris tanpa pasangan pada
merge pandas, dan hasilnya berupa temuan yang diputuskan, misalnya stasiun
baru yang sensor sedang dipasang.
Kesalahan umum#
Kesalahan pertama adalah join tanpa membaca granularitas kedua tabel. Tabel dimensi yang kuncinya tidak unik menggandakan fakta, dan agregasi setelahnya dobel diam-diam, sedangkan pemeriksaan UNIQUE pada dimensi menutupnya sejak awal.
Kesalahan kedua adalah menghitung count(*) pada LEFT JOIN yang hendak
mendeteksi kekosongan. Baris tanpa pasangan tetap berjumlah satu, dan
count(kolom_tabel_kanan) yang bernilai nol adalah sinyal yang benar.
Kesalahan ketiga adalah menulis join berantai tanpa memeriksa jumlah baris antar tahap. Setiap join mengubah granularitas, dan kebiasaan membandingkan count sebelum dan sesudahnya menjaga hasil tetap dapat dipertanggungjawabkan.
Ringkasan#
- Model data dibaca lebih dahulu, dari kunci primer, kunci asing, sampai granularitas satu barisnya.
- Star schema menaruh angka pada fakta dan metadata pada dimensi, dan query agregasinya memotong angka menurut kolom dimensi.
- Inner join menjaga granularitas fakta bila kunci dimensi unik, dan jumlah barisnya diperiksa antar tahap.
- LEFT JOIN dengan count atas kolom kanan menjadi anti-join yang menemukan dimensi tanpa fakta.
Referensi#
- Joins Between Tables — PostgreSQL Documentation — tutorial resmi join pada PostgreSQL.
- The Data Warehouse Toolkit, 3rd Edition — Kimball & Ross, Wiley — rujukan klasik pemodelan dimensi dan star schema.