Dokumentasi dari Chapter Dokumentasi Model Data menjelaskan arti kolom gmv dengan kata-kata. Semantic layer membawa definisi tersebut selangkah lebih jauh: metrik ditulis sekali sebagai kode berstruktur, lalu dieksekusi menjadi angka — sehingga dashboard keuangan dan dashboard marketing tidak mungkin menampilkan GMV yang berbeda. Pada proyek dbt TokoKita, definisi tersebut hidup berdampingan dengan modelnya: merujuk tabel yang sudah dibangun, teruji, dan terdokumentasi. Chapter ini mendefinisikan GMV dalam format semantic layer dbt, lalu mengeksekusinya lewat resolver mini untuk membuktikan konsistensinya.
Posisi Lapisan: Definisi yang Bisa Dieksekusi #
Chapter Semantic Layer pada section Data Modeling dan Data Warehouse memperkenalkan konsepnya lewat SQL view: satu view metrik_bulanan membungkus rumus, dan semua dashboard membaca view tersebut. Pendekatan view berhasil menyeragamkan angka, tetapi view hanya menyimpan teks SQL — alat lain tidak bisa membaca struktur definisinya: measure apa yang dijumlahkan, dimensi apa yang tersedia, filter apa yang wajib. Format dbt mendeklarasikan struktur tersebut secara eksplisit dalam YAML, sehingga definisi metrik bisa dibaca mesin: divalidasi, di-query menurut dimensi mana pun, dan disajikan ke konsumen eksternal.
Semantic model dan metric — pada proyek nyata kerap dipisah menjadi berkasnya masing-masing — bertemu di satu lapisan, lalu tiga konsumen yang berbeda kebutuhan membaca sumber yang sama. Kalau definisi berubah, ketiganya berubah bersama pada saat yang sama.
Semantic Model dan Metric pada YAML #
Definisi GMV TokoKita terdiri dari dua bagian: semantic model yang memetakan fact_pesanan menjadi komponen metrik, dan metric yang merakit komponen tersebut menjadi angka bernama:
# Semantic model + metrik GMV TokoKita (format dbt semantic layer).
version: 2
semantic_models:
- name: pesanan
description: "Fact pesanan TokoKita; grain satu baris per pesanan."
model: ref("fact_pesanan")
defaults:
agg_time_dimension: tanggal
entities:
- name: pesanan
type: primary
expr: id_pesanan
measures:
- name: total_bersih
agg: sum
expr: total
dimensions:
- name: tanggal
type: time
type_params:
time_granularity: day
- name: kota
type: categorical
- name: status
type: categorical
metrics:
- name: gmv
label: GMV
description: "Gross Merchandise Value: total pesanan berstatus selesai."
type: simple
type_params:
measure: total_bersih
filter: "{{ Dimension('pesanan__status') }} = 'selesai'"Semantic model pesanan mendeklarasikan tiga komponen: entity pesanan sebagai kunci utama (id_pesanan), measure total_bersih sebagai agregasi sum atas kolom total, dan dimensions tanggal, kota, status sebagai sumbu pemotongan. Metric gmv lalu merujuk measure tersebut dan menambahkan satu keputusan bisnis: hanya pesanan berstatus selesai yang dihitung. Bandingkan dengan docs block gmv dari Chapter Dokumentasi Model Data — keduanya definisi yang sama, tetapi versi ini berstruktur sehingga bisa dieksekusi, bukan sekadar dibaca.
Resolver: dari Definisi ke SQL #
dbt tidak terpasang di lingkungan bab ini, jadi kita jalankan resolver mini yang meniru inti kerja semantic layer: membaca YAML, me-resolve measure dan filter menjadi komponen SQL, lalu menyusun query untuk setiap permintaan konsumen. Eksekusi berjalan atas fact_pesanan kecil berisi delapan pesanan Juli sampai September 2026; bagian pembuatan tabel dipotong dari tampilan berikut:
import re
import duckdb
import yaml
doc = yaml.safe_load(open("ch3_semantic.yml", encoding="utf-8"))
sm = doc["semantic_models"][0]
metrik = doc["metrics"][0]
# -- resolusi komponen definisi, seperti yang dbt semantic layer lakukan --
tabel = sm["model"].replace('ref("', "").replace('")', "")
ukuran = sm["measures"][0] # total_bersih: sum(total)
kolom_ukur = ukuran["expr"]
agregasi = ukuran["agg"]
# filter metrik: {{ Dimension('pesanan__status') }} = 'selesai'
flt = metrik["filter"]
nama_dim = re.search(r"Dimension\('(\w+)'\)", flt).group(1) # pesanan__status
kolom_filter = nama_dim.split("__")[1] # status
nilai_filter = re.search(r"}}\s*=\s*'(\w+)'", flt).group(1) # selesai
kondisi = f"{kolom_filter} = '{nilai_filter}'"
print(f"metric : {metrik['name']} ({metrik['label']})")
print(f"definisi : {agregasi.upper()}({kolom_ukur}) WHERE {kondisi}")
print(f"tabel : {tabel}")
print()def gmv_kelompok(ekspresi, label):
"""Susun SQL dari definisi metric, kelompokkan sesuai permintaan konsumen."""
sql = (f"SELECT {ekspresi} AS {label}, "
f"{agregasi.upper()}({kolom_ukur}) AS gmv "
f"FROM {tabel} WHERE {kondisi} GROUP BY {ekspresi} ORDER BY {ekspresi}")
print(f"-- konsumen meminta: GMV per {label}")
print(sql)
hasil = con.execute(sql).fetchall()
for baris in hasil:
print(" ", baris)
return sum(baris[1] for baris in hasil)
t1 = gmv_kelompok("strftime('%Y-%m', tanggal)", "bulan")
print()
t2 = gmv_kelompok("kota", "kota")
print()
print(f"total per bulan = {t1}, total per kota = {t2}, identik: {t1 == t2}")
print(f"pembanding tanpa filter status: ", end="")
print(con.execute("SELECT SUM(total) FROM fact_pesanan").fetchone()[0])Output yang Dihasilkan:
metric : gmv (GMV)
definisi : SUM(total) WHERE status = 'selesai'
tabel : fact_pesanan
-- konsumen meminta: GMV per bulan
SELECT strftime('%Y-%m', tanggal) AS bulan, SUM(total) AS gmv FROM fact_pesanan WHERE status = 'selesai' GROUP BY strftime('%Y-%m', tanggal) ORDER BY strftime('%Y-%m', tanggal)
('2026-07', 585000)
('2026-08', 995000)
('2026-09', 870000)
-- konsumen meminta: GMV per kota
SELECT kota AS kota, SUM(total) AS gmv FROM fact_pesanan WHERE status = 'selesai' GROUP BY kota ORDER BY kota
('Bandung', 340000)
('Jakarta', 1000000)
('Surabaya', 1110000)
total per bulan = 2450000, total per kota = 2450000, identik: True
pembanding tanpa filter status: 2845000Mari kita telaah bagian-bagian penting skrip di atas:
- Blok pertama, baris 10-13 (resolusi measure):
ref("fact_pesanan")dibersihkan menjadi nama tabel, laluexprdanaggdari measure menghasilkan komponenSUM(total). - Blok pertama, baris 16-20 (resolusi filter): ekspresi reguler mengekstrak nama dimensi
pesanan__statusdan nilai'selesai'dari filter metrik; konvensi penamaanmodel__dimensidipecah menjadi kolomstatus, dan kondisi SQL terbentuk. - Blok kedua, baris 3-6 (
gmv_kelompok): SQL disusun sepenuhnya dari komponen definisi — hanya ekspresi pengelompokan yang berasal dari konsumen, sehingga filter dan agregasi tidak mungkin berbeda antar permintaan. - Blok kedua, baris 18-20 (pemeriksaan konsistensi): total kedua pengelompokan dibandingkan, lalu pembanding tanpa filter status dihitung sebagai kontrol.
Angka yang Sama, Terbukti #
Dua konsumen menghasilkan total 2.450.000 yang identik — bukan kebetulan, melainkan konsekuensi satu definisi. Pembanding di baris terakhir menghasilkan 2.845.000; selisih 395.000 berasal dari dua pesanan berstatus dibatalkan (ORD-3 sebesar Rp300.000 dan ORD-6 sebesar Rp95.000). Query ad-hoc yang lupa menyaring status seperti itu tidak akan tertangkap dbt test dari Chapter Testing Model Data karena query tersebut bukan model — justru pada celah inilah semantic layer bekerja: selama konsumen memanggil metrik bernama gmv, filter status tidak mungkin dilupakan.
dbt Semantic Layer pada Ekosistem #
Pada proyek dbt nyata, definisi seperti di atas divalidasi saat dbt parse dan dbt build — nama measure yang salah ketik, dimensi yang tidak terdaftar, atau tipe metrik yang keliru langsung digagalkan sebelum menyentuh data. Arah ekosistemnya lebih jauh lagi: dbt Semantic Layer mengekspos metrik-metrik tersebut lewat API, sehingga alat BI cukup memanggil “GMV per bulan” tanpa menulis SQL sama sekali. Struktur deklarasi semantic model dan metric dirinci di dokumentasi semantic models
serta dokumentasi metrics
dbt.
Definisi metrik kini tunggal dan siap dipanggil. Pertanyaan berikutnya bersifat praktis: bagaimana alat BI — Metabase, Tableau, Looker — menemukan model-model tersebut dan membacanya dengan aman? Materi Chapter Integrasi Business Intelligence menjawabnya.