TL;DR
Model dbt adalah satu file SQL berisi satu perintah SELECT yang dijalanin dbt jadi tabel atau view di warehouse kamu. Buat mulai: install dbt Core, atur koneksi di profiles.yml, taruh file .sql di folder models, lalu jalankan dbt run. Referensi antar model pakai fungsi ref supaya dbt tau urutan jalannya.
Model dbt adalah satu file SQL yang isinya satu perintah SELECT, dan dbt yang ngubah hasilnya jadi tabel atau view di database kamu.
Kedengeran sederhana, dan memang iya. Yang bikin dbt kepakai di banyak tim itu bukan fiturnya yang ribet, tapi karena semua transformasi jadi file SQL yang bisa dibaca, direview, dan dijalanin ulang dengan urutan yang benar.
Di tutorial ini kamu bakal install dbt, nyambungin ke database, terus bikin dua model dari data toko_berkah sampai keluar tabel penjualan harian per cabang.
dbt adalah alat buat ngatur transformasi data di dalam warehouse. Kamu nulis SQL, dbt yang ngurus urutan jalannya, bikin tabel atau view-nya, dan nyatet hasilnya. Dia nggak mindahin data dari sumber ke warehouse, dan nggak nyimpen data sendiri. Tugasnya cuma satu: ngerapikan data yang udah masuk.
Bedanya sama nulis SQL manual di editor database, ada tiga.
Pertama, urutannya otomatis. Kalau model B butuh hasil model A, dbt jalanin A duluan tanpa kamu atur manual.
Kedua, kodenya masuk Git. Bisa direview, bisa dibalikin kalau salah.
Ketiga, ada test bawaan buat ngecek angkanya masuk akal sebelum masuk dashboard. Bagian ini aku bahas terpisah di dbt tests buat cegah angka salah.
Empat hal, dan semuanya gratis:
transaksi_rawDatanya sendiri sederhana. Tabel transaksi_raw punya 115.482 baris transaksi kasir toko_berkah dari Januari 2024 sampai Desember 2025, dengan kolom id_transaksi, tanggal, nama_cabang, id_produk, qty, dan harga_satuan.
Datanya kotor, dan itu justru bagusnya buat latihan. Nama cabang ditulis campur huruf besar kecil, ada 1.204 baris dengan qty nol, dan 87 baris tanggalnya kosong.
Install lewat pip, sekalian sama adapter database yang kamu pakai. Satu perintah:
pip install dbt-core dbt-postgres
Kalau kamu pakai DuckDB, ganti jadi dbt-duckdb. Kalau BigQuery, dbt-bigquery.
Cek berhasil atau nggak:
dbt --version
Kalau keluar nomor versi, aman. Kalau muncul command not found, biasanya folder script Python belum masuk PATH.
Jalankan perintah ini di folder tempat kamu mau naruh proyeknya:
dbt init toko_berkah_dbt
dbt bakal nanya beberapa hal: jenis database, host, port, user, password, nama database, dan skema tujuan. Isi sesuai setelan database kamu.
Hasilnya folder dengan struktur begini:
toko_berkah_dbt/
├── dbt_project.yml
├── models/
│ └── example/
├── seeds/
├── macros/
└── tests/
Hapus dulu isi folder models/example biar nggak ganggu.
Koneksi database disimpan di file profiles.yml, letaknya di folder ~/.dbt/ pada Mac dan Linux, atau C:\Users\namamu\.dbt\ di Windows. Isinya kira-kira gini:
toko_berkah_dbt:
target: dev
outputs:
dev:
type: postgres
host: localhost
port: 5432
user: bima
password: rahasia
dbname: analitik
schema: dbt_bima
threads: 4
Tes koneksinya:
dbt debug
Semua baris harus OK. Kalau connection test gagal, cek dulu host dan port-nya.
Source itu cara dbt nunjuk ke tabel yang datanya bukan dia yang bikin. Bikin file models/staging/sumber.yml:
version: 2
sources:
- name: toko_berkah
schema: raw
tables:
- name: transaksi_raw
- name: produk_raw
Setelah didaftarin, kamu manggilnya pakai {{ source('toko_berkah', 'transaksi_raw') }}, bukan nama tabel langsung. Gunanya biar dbt bisa nunjukin dari mana asal data tiap model waktu kamu buka dokumentasinya nanti.
Model pertama biasanya model staging, yaitu model yang tugasnya cuma ngerapikan satu tabel mentah. Ganti nama kolom, benerin tipe data, buang baris sampah. Nggak ada agregasi di sini.
Bikin file models/staging/stg_transaksi.sql:
with sumber as (
select * from {{ source('toko_berkah', 'transaksi_raw') }}
)
select
cast(id_transaksi as varchar) as id_transaksi,
cast(tanggal as date) as tanggal,
trim(upper(nama_cabang)) as nama_cabang,
cast(id_produk as varchar) as id_produk,
cast(qty as integer) as qty,
cast(harga_satuan as numeric) as harga_satuan,
cast(qty as integer) * cast(harga_satuan as numeric) as total_rupiah
from sumber
where tanggal is not null
and qty > 0
Perhatiin tiga hal di query ini.
Nggak ada titik koma di akhir. dbt yang nambahin sendiri waktu ngirim ke database.
Nggak ada CREATE TABLE. Cukup SELECT, sisanya urusan dbt.
trim(upper(nama_cabang)) yang benerin masalah nama cabang ditulis lima versi. Setelah baris ini jalan, 12 cabang toko_berkah yang tadinya kebaca 27 nama unik jadi 12 lagi.
Di sinilah fungsi ref kepakai. Bikin file models/marts/penjualan_harian_cabang.sql:
with transaksi as (
select * from {{ ref('stg_transaksi') }}
)
select
tanggal,
nama_cabang,
count(distinct id_transaksi) as jumlah_struk,
sum(qty) as total_unit,
sum(total_rupiah) as omzet_rupiah,
round(sum(total_rupiah) / nullif(count(distinct id_transaksi), 0), 0) as rata_rata_struk
from transaksi
group by tanggal, nama_cabang
order by tanggal desc, omzet_rupiah desc
{{ ref('stg_transaksi') }} nunjuk ke model sebelumnya. Dari sini dbt otomatis ngerti bahwa stg_transaksi harus jalan duluan.
nullif dipakai biar nggak error waktu jumlah struk nol. Ini kebiasaan bagus yang mirip fungsi IFERROR di Excel, cuma versi SQL-nya.
Default dbt itu view. Buat model marts yang sering dipanggil dashboard, mending disimpan jadi tabel biar cepat.
Buka dbt_project.yml, tambahin di bagian bawah:
models:
toko_berkah_dbt:
staging:
+materialized: view
marts:
+materialized: table
Kalau cuma mau ngatur satu model, taruh di atas file SQL-nya:
{{ config(materialized='table') }}
Dari folder proyek, ketik:
dbt run
Output-nya kurang lebih gini:
Running with dbt=1.9.0
Found 2 models, 2 sources
1 of 2 START sql view model dbt_bima.stg_transaksi ........ [RUN]
1 of 2 OK created sql view model dbt_bima.stg_transaksi ... [CREATE VIEW in 0.42s]
2 of 2 START sql table model dbt_bima.penjualan_harian_cabang [RUN]
2 of 2 OK created sql table model dbt_bima.penjualan_harian_cabang [SELECT 8760 in 1.91s]
Completed successfully
Mau jalanin satu model aja? Pakai flag select:
dbt run --select penjualan_harian_cabang
Mau jalanin satu model plus semua yang bergantung padanya:
dbt run --select stg_transaksi+
Buat daftar lengkap opsi seleksi, cek dokumentasi resmi dbt soal models.
Setelah dua model itu jalan, tabel penjualan_harian_cabang isinya 8.760 baris, yaitu 730 hari dikali 12 cabang.
Angka yang keluar dari data ini:
| Cabang | Omzet 2025 (Rp) | Rata-rata struk (Rp) | Jumlah struk |
|---|---|---|---|
| SOLO PUSAT | 1.842.300.000 | 78.400 | 23.499 |
| SEMARANG BARAT | 1.317.600.000 | 71.200 | 18.506 |
| KLATEN | 612.900.000 | 52.100 | 11.764 |
Yang menarik bukan omzetnya, tapi rata-rata struknya. Selisih SOLO PUSAT sama KLATEN itu Rp26.300 per struk, atau 50 persen lebih tinggi.
Waktu aku pecah lagi per kategori produk, penyebabnya ketemu: 41 persen omzet SOLO PUSAT datang dari kategori paket sembako besar, sementara di KLATEN cuma 12 persen. Cabang Klaten jual banyak barang, tapi barang kecil.
Insight kayak gini yang susah kelihatan waktu datanya masih di 115 ribu baris mentah. Begitu diringkas jadi model harian per cabang, selisihnya langsung nongol.
1. Nulis nama tabel langsung, bukan ref. Model bakal tetap jalan, tapi dbt nggak ngerti urutannya. Begitu proyek kamu punya 20 model, ini bakal jadi masalah.
2. Naruh semua logika di satu model raksasa. Pecah jadi staging dan marts. Staging buat bersihin, marts buat ngitung.
3. Pakai titik koma di akhir query. dbt bakal error karena dia nambahin pembungkus sendiri di sekitar SELECT kamu.
4. Nggak ngecek jumlah baris setelah filter. Filter qty > 0 di contoh tadi motong 1.204 baris. Kalau kamu nggak sadar, angka omzetnya jadi beda dari laporan kasir dan kamu bakal bingung nyari selisihnya.
5. Bikin model marts sebelum data mentahnya bersih. Kalau nama cabang belum distandarkan, GROUP BY kamu bakal ngasilin cabang hantu. Cek dulu soal kualitas data sebelum ngitung.
6. Ngira dbt bisa narik data dari sumber. Nggak bisa. Data harus udah masuk warehouse duluan lewat proses ETL atau tools ingest terpisah.
Nggak. dbt cuma ngirim perintah SQL ke database yang udah kamu punya. Dia nggak nyimpen data apa-apa. Jadi kamu tetap butuh PostgreSQL, BigQuery, Snowflake, atau DuckDB sebagai tempat datanya. dbt kerjanya ngatur urutan, ngubah file SQL kamu jadi tabel, dan nyatet apa yang udah jalan.
Nggak harus. Yang kamu tulis di dbt itu SQL, bukan Python. Python cuma dipakai sekali waktu install lewat pip. Kalau kamu udah nyaman nulis SELECT, JOIN, dan GROUP BY, kamu udah punya modal cukup buat bikin model pertama hari ini juga.
dbt Core itu versi gratis yang kamu jalanin dari terminal di komputer sendiri. dbt Cloud versi berbayar dengan editor di browser, penjadwal otomatis, dan dokumentasi yang di-hosting. Buat belajar dan proyek kecil, dbt Core udah cukup. Naik ke Cloud kalau kamu butuh jadwal jalan otomatis tanpa ngurus server.
Soalnya ref bikin dbt tau model mana yang harus jalan duluan. Kalau kamu tulis nama tabel mentah, dbt nggak ngerti hubungan antar model dan urutannya bisa kacau. ref juga otomatis nyesuain nama skema waktu kamu pindah dari lingkungan development ke production, jadi kodenya nggak perlu diedit.
Dua-duanya bisa, tergantung setelan materialized. Default-nya view, yang artinya query dijalanin ulang tiap kali dipanggil dan nggak makan storage. Kalau datanya besar atau query-nya lambat, ganti ke table biar hasilnya disimpan fisik. Setelannya bisa ditaruh di dbt_project.yml atau langsung di file model.
Yang perlu kamu bawa dari sini:
ref antar model, dan source buat tabel mentah.Langkah berikutnya, tambahin test biar angka yang salah ketahan sebelum sampai dashboard. Aku tulis caranya di dbt tests: mencegah angka salah sampai ke dashboard.
Kalau kamu belum punya database buat latihan, bikin dulu satu tabel dari file CSV penjualan kamu sendiri. Sepuluh kolom juga cukup buat model pertama.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Kolom datetime nyimpen tanggal, jam, hari, semuanya nempel jadi satu. Accessor .dt di pandas ngeluarin tiap bagian jadi kolom sendiri buat dianalisa.
Nama toko ketik campur huruf besar-kecil, spasi nyasar di ujung, atau kode produk nempel jadi satu. Accessor .str di pandas ngerapiin semua itu tanpa loop.
Model machine learning cuma ngerti angka, bukan teks kayak merah atau biru. get_dummies ngubah kolom kategori jadi kolom 0/1 dalam satu baris kode.