Dasar dbt

Dasar dbt, yaitu model, ref, dan source pada proyek pertama.

Daftar isi

Transformasi bergaya ELT menempatkan pekerjaan SQL di dalam warehouse, dan dbt (data build tool) adalah alat yang mempopulerkan cara kerja tersebut. Topik ini membahas tiga konsep inti dbt, yaitu model sebagai berkas SQL yang dimaterialisasi di warehouse, source sebagai deklarasi tabel mentah yang dibaca, dan ref sebagai penghubung antar model. Proyek dbt pertama ditampilkan lengkap dengan pohon berkasnya dan konfigurasinya, kemudian semantik kedua modelnya dijalankan sungguhan dengan DuckDB pada data kecil, sehingga cara ref dan source bekerja terlihat tanpa memasang dbt Core sama sekali.

Model: SELECT yang dimaterialisasi#

Model dbt (model) adalah berkas berisi satu pernyataan SELECT yang oleh dbt dibungkus menjadi pembuatan objek di warehouse, proses yang disebut materialisasi (materialization). Nama berkasnya menjadi nama relasi hasilnya, sehingga berkas stg_pesanan.sql menghasilkan relasi bernama stg_pesanan. Bentuk materialisasi ditentukan konfigurasi, dengan pilihan yang paling umum berupa view untuk model antara dan table untuk model akhir, sedangkan folder tempat model disimpan ditunjuk oleh kunci model-paths pada dbt_project.yml.

Proyek dbt pertama cukup berisi pohon berkas berikut beserta konfigurasinya.

text Pohon proyek dbt pertama
analitik_pesanan/
├── dbt_project.yml
└── models/
    ├── staging/
    │   ├── sources.yml
    │   └── stg_pesanan.sql
    └── marts/
        └── ringkasan_pesanan.sql
yaml dbt_project.yml
name: analitik_pesanan
version: "1.0.0"
config-version: 2

profile: analitik_pesanan

model-paths: ["models"]

models:
  analitik_pesanan:
    staging:
      +materialized: view
    marts:
      +materialized: table

Kunci models pada berkas konfigurasi itu menetapkan materialisasi per folder, sehingga seluruh model pada folder staging menjadi view dan seluruh model pada folder marts menjadi table tanpa menuliskan konfigurasi di setiap berkas SQL. Konfigurasi profile menunjuk koneksi ke warehouse yang dikelola dbt, sedangkan name menjadi nama proyek yang dipakai dbt di seluruh berkas konfigurasinya. Dua hal tersebut diperiksa lebih dulu oleh perintah dbt debug sebelum model apa pun dibangun.

bash
# tidak-dijalankan
# seluruh perintah dbt dijalankan dari folder proyek setelah dbt Core terpasang
dbt debug   # memeriksa struktur proyek dan koneksi ke warehouse
dbt run     # membangun seluruh model sesuai urutan dependensinya

Source: pintu masuk data mentah#

Source adalah deklarasi untuk tabel mentah yang tidak dibangun oleh dbt, misalnya tabel hasil replikasi dari basis data operasional. Deklarasinya ditulis pada berkas YAML, lalu model merujuknya lewat fungsi source() dengan menyebut nama sumber dan nama tabelnya. Pemakaian source membuat setiap model mencatat asal datanya, sehingga garis keturunan data (lineage) sampai ke tabel mentah tetap terbaca, dan perubahan nama tabel di sumber cukup diperbaiki pada satu tempat.

Sebuah sumber juga dapat membawa aturan pengujian dan konfigurasi kesegaran yang menandai tabel mentah yang terlambat diperbarui. Pada proyek contoh, tabel pelanggan ikut dideklarasikan sejak awal karena pengujian relationships pada topik Pengujian dan dokumentasi dbt memerlukan tabel pelanggan sebagai acuan. Dengan demikian, berkas sources.yml menjadi daftar tunggal atas seluruh tabel mentah yang dipakai proyek.

yaml models/staging/sources.yml
version: 2

sources:
  - name: operasional
    description: "Basis data OLTP yang direplikasi ke warehouse."
    schema: operasional
    tables:
      - name: pesanan
        description: "Baris pesanan mentah dari sistem transaksi."
      - name: pelanggan
        description: "Daftar pelanggan sebagai acuan kunci asing."
sql models/staging/stg_pesanan.sql
-- tidak-dijalankan
-- model staging: satu SELECT yang dimaterialisasi dari sumber
select
    id as id_pesanan,
    tanggal as tanggal_pesanan,
    id_pelanggan,
    jumlah_item,
    status
from {{ source('operasional', 'pesanan') }}

Model stg_pesanan.sql memilih dan menamai ulang kolom yang dibutuhkan dari sumber operasional.pesanan. Model staging semacam ini menjadi satu-satunya lapisan yang membaca bentuk asli tabel mentah, sehingga model lain tidak pernah menyentuh tabel sumber secara langsung. Pemisahan ini membuat perubahan di sumber hanya menghantam satu tempat, yaitu model staging yang menamai ulang kolomnya.

Ref: dependensi antar model#

Fungsi ref() merujuk model lain dan sekaligus menjadi pernyataan dependensi. dbt membaca seluruh pemanggilan ref untuk menyusun DAG (directed acyclic graph), yaitu graf dependensi yang tidak membentuk lingkaran, kemudian membangun model dalam urutan yang memenuhi seluruh dependensinya. Model yang bergantung pada dua model lain otomatis dibangun setelah keduanya selesai, tanpa urutan yang ditulis manual. Karena itu, nama relasi model lain tidak pernah ditulis langsung pada FROM, melainkan selalu melalui ref.

sql models/marts/ringkasan_pesanan.sql
-- tidak-dijalankan
-- model mart: membaca model lain lewat ref, bukan nama tabel mentah
select
    tanggal_pesanan,
    count(*) as jumlah_pesanan,
    sum(jumlah_item) as total_item
from {{ ref('stg_pesanan') }}
group by tanggal_pesanan

Pada model ringkasan_pesanan.sql, pemanggilan ref dengan argumen stg_pesanan menyatakan bahwa model ini bergantung pada model staging yang sudah ditampilkan. dbt mengurutkan pembangunannya secara otomatis, yaitu stg_pesanan lebih dahulu dan ringkasan_pesanan kemudian. Jika kelak sebuah model dimasukkan di antara keduanya, cukup argumen ref yang diubah, dan urutan pembangunannya mengikuti. Pemakaian ref juga membuat dbt tidak membangun model yang dependensinya gagal dibangun lebih dulu, karena kegagalan model staging menghentikan model yang bergantung padanya.

Demonstrasi semantik dengan DuckDB#

Perintah dbt tidak dapat dijalankan pada lingkungan kerja jalur ini karena dbt Core tidak terpasang, sedangkan mekanisme source dan ref dapat dibuktikan dengan menjalankan SQL model yang sama pada DuckDB. Skrip berikut membuat tabel sumber yang kecil, lalu menjalankan SQL kedua model persis sebagaimana tertulis, dengan satu perbedaan, yaitu pemanggilan source() dan ref() digantikan dengan nama relasi yang sesungguhnya. Penggantian itulah yang dbt lakukan sebelum SQL dikirim ke warehouse.

python demo-semantik-model.py
"""Simulasi semantik dua model dbt dengan DuckDB.

dbt merender pemanggilan source() dan ref() menjadi nama relasi yang
sesungguhnya sebelum SQL dikirim ke warehouse. Skrip ini menjalankan SQL
kedua model pada data kecil, dengan jinja sudah digantikan namanya.
"""

import duckdb

kon = duckdb.connect()

# sumber: tabel operasional yang biasanya disediakan replikasi data
kon.execute(
    """
    create table pesanan as
    select * from (values
        (1, date '2026-11-01', 'C01', 3, 'BARU'),
        (2, date '2026-11-01', 'C02', 1, 'BARU'),
        (3, date '2026-11-02', 'C01', 2, 'SELESAI'),
        (4, date '2026-11-02', 'C03', 5, 'SELESAI')
    ) as t(id, tanggal, id_pelanggan, jumlah_item, status)
    """
)

# model stg_pesanan: source('operasional', 'pesanan') menjadi tabel pesanan
kon.execute(
    """
    create view stg_pesanan as
    select
        id as id_pesanan,
        tanggal as tanggal_pesanan,
        id_pelanggan,
        jumlah_item,
        status
    from pesanan
    """
)

# model ringkasan_pesanan: ref('stg_pesanan') menjadi view stg_pesanan
kon.execute(
    """
    create table ringkasan_pesanan as
    select
        tanggal_pesanan,
        count(*) as jumlah_pesanan,
        sum(jumlah_item) as total_item
    from stg_pesanan
    group by tanggal_pesanan
    """
)

print("model stg_pesanan (materialisasi view):")
print(f"{'id_pesanan':>10} {'tanggal_pesanan':>16} {'id_pelanggan':>13} "
      f"{'jumlah_item':>12} {'status':>8}")
for id_, tanggal, pelanggan, jumlah, status in kon.execute(
    "select * from stg_pesanan order by id_pesanan"
).fetchall():
    print(f"{id_:>10} {str(tanggal):>16} {pelanggan:>13} {jumlah:>12} {status:>8}")

print()
print("model ringkasan_pesanan (materialisasi table):")
for tanggal, n_pesanan, total_item in kon.execute(
    """
    select tanggal_pesanan, jumlah_pesanan, total_item
    from ringkasan_pesanan
    order by tanggal_pesanan
    """
).fetchall():
    print(f"{tanggal}  {n_pesanan} pesanan  {total_item} item")
text Keluaran
model stg_pesanan (materialisasi view):
id_pesanan  tanggal_pesanan  id_pelanggan  jumlah_item   status
         1       2026-11-01           C01            3     BARU
         2       2026-11-01           C02            1     BARU
         3       2026-11-02           C01            2  SELESAI
         4       2026-11-02           C03            5  SELESAI

model ringkasan_pesanan (materialisasi table):
2026-11-01  2 pesanan  4 item
2026-11-02  2 pesanan  7 item

Keluaran memperlihatkan kedua model dengan isi yang benar. Perhitungan jumlah pesanan dan total item per tanggal hanya mungkin bila stg_pesanan sudah tersedia lebih dulu, dan urutan itulah yang dijaga dbt lewat ref. Pada proyek yang sesungguhnya, perintah dbt run membuat view stg_pesanan lalu table ringkasan_pesanan di warehouse, dengan bentuk materialisasinya dibaca dari dbt_project.yml. Simulasi ini sekaligus menegaskan bahwa model dbt hanyalah SQL biasa, karena tanpa jinja dan tanpa dbt, SQL-nya tetap dapat dieksekusi oleh basis data apa pun yang mendukungnya.

Kesalahan umum#

  1. Menulis nama relasi model lain secara langsung pada FROM. Penyebabnya, kebiasaan SQL mendorong jalan pintas daripada menuliskan fungsi ref. Akibatnya, dbt tidak mengenali dependensinya, sehingga model dapat dibangun tanpa urutan yang benar dan DAG-nya terputus. Perbaikannya, seluruh rujukan antar model melalui ref, dan seluruh tabel mentah melalui source.
  2. Membaca tabel mentah langsung dari model akhir. Penyebabnya, model mart ditulis lebih dulu sebelum lapisan staging disiapkan. Akibatnya, bentuk asli tabel sumber menyebar ke banyak model, dan perubahan nama kolom di sumber menghantam semuanya sekaligus. Perbaikannya, lalui data mentah lewat model staging, lalu model akhir hanya membaca model staging.
  3. Menganggap berkas model otomatis ter-update di warehouse. Penyebabnya, model berupa berkas SQL yang disunting pada editor, sehingga pekerjaan terasa selesai begitu berkasnya tersimpan. Akibatnya, relasi di warehouse masih berisi hasil run sebelumnya sampai dbt dijalankan lagi. Perbaikannya, jalankan dbt run setelah setiap perubahan model, atau ikat perintah itu pada jadwal orkestrator.

Ringkasan#

  • Model dbt adalah berkas SELECT yang dimaterialisasi menjadi view atau table, dan materialisasi per folder dikonfigurasi pada dbt_project.yml.
  • Source mendeklarasikan tabel mentah yang tidak dibangun dbt, sedangkan ref merujuk model lain dan sekaligus menjadi dasar penyusunan DAG.
  • Simulasi DuckDB membuktikan semantiknya, yaitu pemanggilan source dan ref digantikan dengan nama relasi yang sesungguhnya, dan urutan pembangunan mengikuti dependensi ref.

Referensi#