পাঠ ১৫ · ২৯-এর মধ্যে · মডিউল ২

dbt — analytics engineering

dbt — SQL-based transformation, tests, materialization
৮ মিনিট পড়া মাঝারি · Intermediate SQL-only

এই পাঠে যা শিখবেন

  • "Analytics engineer" role — dbt কেন এটি জন্ম দিল
  • Model, ref, source — তিন core concept
  • Materialization choice — কোনটি কখন
  • Tests, snapshots, documentation — production-grade analytics

১ · Analytics engineer — নতুন একটি role

২০২০-র আগে dataworld-এ দু'টি প্রধান role: data engineer (pipeline বানায়, Python/Spark) ও data analyst (BI dashboard, SQL)। মাঝে একটি gap — কে warehouse-এ SQL transformation বানাবে?

dbtdbt (data Build Tool)২০১৬-তে Fishtown Analytics (এখন dbt Labs)-এ Tristan Handy তৈরি। Open-source dbt Core + paid dbt Cloud। আজকের modern data stack-এর কেন্দ্রীয় উপাদান। এই gap পূরণ করেছে — এবং একটি নতুন role জন্ম দিয়েছে: analytics engineer। SQL জানে, software engineering practice (Git, test, CI/CD) জানে, business context বোঝে।

dbt-এর philosophy

১) SELECT-only: কোনো INSERT/UPDATE/DELETE নয়। dbt-ই DDL handle করে।
২) Modular: ছোট ছোট model — একটিতে এক transformation।
৩) Testable: প্রতিটি model-এ assertion (unique, not-null, relationship)।
৪) Documented: YAML-এ description; auto-generated docs site।

২ · একটি dbt model দেখা যাক

Daraz-এর order data থেকে customer summary বানাচ্ছি — তিন stage-এ:

SQL · dbt model · models/staging/stg_orders.sql
{{ config(materialized='view') }}

-- Source থেকে clean staging layer
select
    order_id,
    customer_id,
    cast(order_date as date)            as order_date,
    cast(amount as numeric(18, 2))      as amount_bdt,
    lower(trim(status))                 as status
from {{ source('raw', 'orders') }}
where order_date is not null

    
এক ফাইল = এক model = এক SELECT। dbt run করলে warehouse-এ stg_orders নামে view তৈরি। {{ source('raw', 'orders') }} = Jinja templating, dbt resolve করে actual table name-এ।

৩ · ref() — dbt-এর সবচেয়ে গুরুত্বপূর্ণ function

একটি model অন্য model-এ depend করে। কিন্তু hard-code table name লেখা ভঙ্গুর — dev/prod-এ আলাদা schema, naming convention বদলায়।

{{ ref('model_name') }} — এই magic function দু'টি কাজ করে:

  • Model name → actual fully-qualified table name resolve।
  • Dependency graph (DAG) auto-build। dbt জানে কোনটা আগে run।
SQL · models/marts/customer_summary.sql
{{ config(materialized='table') }}

-- প্রতিটি customer-এর LTV summary
with orders as (
    select * from {{ ref('stg_orders') }}
    where status = 'completed'
)

select
    customer_id,
    count(*)                  as order_count,
    sum(amount_bdt)           as lifetime_value_bdt,
    avg(amount_bdt)           as avg_order_value,
    min(order_date)           as first_order,
    max(order_date)           as last_order,
    current_date - max(order_date) as days_since_last_order
from orders
group by customer_id

    
{{ ref('stg_orders') }} দেখে dbt বুঝবে — customer_summary stg_orders-এর উপর depend। তাই dbt run করলে — আগে stg_orders, পরে customer_summary।

৪ · Materialization — চারটি option

একই SELECT কিন্তু dbt warehouse-এ চার ভিন্ন রকম materialize করতে পারে:

  • view: warehouse-এ CREATE VIEW। Storage cost নেই, কিন্তু query-time-এ run। ছোট, frequently changing data-তে।
  • table: CREATE TABLE AS SELECT। প্রতি dbt run-এ পুরো rebuild। Read fast, কিন্তু rebuild expensive।
  • incremental: প্রথমবার full table; পরে শুধু new/changed row append। বড় fact table-এর জন্য আদর্শ।
  • ephemeral: CTE হিসেবে inline। কোনো object তৈরি হয় না। Helper কাজে।

Incremental example:

SQL · models/marts/fct_transactions.sql
{{ config(
    materialized='incremental',
    unique_key='transaction_id',
    on_schema_change='append_new_columns'
) }}

select
    transaction_id,
    user_id,
    amount,
    transaction_at,
    -- bKash style: derived columns
    case when amount > 50000 then 'high' else 'normal' end as size_bucket
from {{ ref('stg_transactions') }}

{% if is_incremental() %}
    -- শুধু last run-এর পর-এর data
    where transaction_at >= (
        select coalesce(max(transaction_at), '1900-01-01')
        from {{ this }}
    )
{% endif %}

    
{% if is_incremental() %} — Jinja template। প্রথমবার run-এ table-টা যেহেতু নেই, এই block skip; full data load। পরের run-এ শুধু new transaction। unique_key দিয়ে duplicate handle।
Incremental সাবধানতা: Late-arriving data (যেমন ১ দিন পরে আসা refund) miss হবে। Lookback window রাখুন: where transaction_at >= max - interval '3 days'। বা full_refresh মাঝে মাঝে।

৫ · Tests — data quality assertion

dbt-এর সবচেয়ে loved feature — built-in test। YAML-এ একটা লাইন লিখলে — প্রতিটি run-এ assertion check।

YAML · models/marts/_marts.yml
version: 2

models:
  - name: customer_summary
    description: "Daraz customer LTV — refreshed daily"
    columns:
      - name: customer_id
        description: "Unique customer identifier"
        tests:
          - unique
          - not_null
      - name: order_count
        tests:
          - not_null
          - dbt_utils.accepted_range:
              min_value: 1
      - name: lifetime_value_bdt
        tests:
          - not_null

  - name: fct_transactions
    columns:
      - name: transaction_id
        tests:
          - unique
          - not_null
      - name: user_id
        tests:
          - not_null
          - relationships:
              to: ref('dim_users')
              field: user_id

    
Built-in tests: unique, not_null, accepted_values, relationships. dbt test command এ সব test চলে; fail হলে exit code non-zero — CI/CD ভাঙে। dbt_utils package-এ আরও ১০০+ test।

৬ · Sources — raw layer-এর declaration

dbt-এ raw data (ELT-এর "EL" থেকে আসা) "source" হিসেবে declare করতে হয়। YAML-এ:

YAML · models/staging/_sources.yml
version: 2

sources:
  - name: raw
    description: "Raw data ingested from operational systems"
    schema: raw_daraz
    tables:
      - name: orders
        description: "Order events from MySQL — ingested by Fivetran"
        loaded_at_field: _fivetran_synced
        freshness:
          warn_after: {count: 12, period: hour}
          error_after: {count: 24, period: hour}
        columns:
          - name: order_id
            tests:
              - unique
              - not_null

      - name: customers
        loaded_at_field: updated_at

    
dbt source freshness — raw table-এ recent load-এর check। ১২ ঘণ্টা না update হলে warning, ২৪ ঘণ্টায় error। Pipeline upstream broken indicator।
dbt project — three layered architecture sources → staging → intermediate → marts 📥 sources raw_daraz.orders 🧹 staging stg_orders (view) 🔧 intermediate int_orders_joined 📊 marts fct_revenue declared in YAML clean + cast join + enrich BI-ready 🧪 Tests · 📚 Docs · 🔁 CI/CD · 📅 Schedule (Airflow / dbt Cloud) unique · not_null · relationships · freshness · custom SQL tests 🗄️ Warehouse: Snowflake / BigQuery / Redshift / Postgres all transformations run as SQL inside the warehouse dbt Core: free CLI · dbt Cloud: managed scheduler + IDE "transform-in-place" — data ingest আগে, transform পরে = ELT
dbt-এর recommended layered structure: source → staging → intermediate → marts। প্রতিটি layer dbt-এর ভেতরে SQL model।

৭ · Snapshot — slowly changing dimension

Daraz-এ আজ একটি customer-এর address "Dhaka", আগামীকাল "Chattogram"। Old order Dhaka-এ মেপে দেখাবেন না Chattogram-এ? — এটি classic SCD (Slowly Changing Dimension) সমস্যা।

Snapshotdbt SnapshotSCD type-2 implementation। প্রতিটি change-এ valid_from / valid_to দিয়ে history track। dbt automatic detect ও handle করে। = SCD type-2-এর dbt implementation। প্রতি change-এ history-তে নতুন row যোগ; পুরাতন row-এ valid_to set।

{% snapshot customers_snapshot %}
{{ config(
    target_schema='snapshots',
    unique_key='customer_id',
    strategy='timestamp',
    updated_at='updated_at'
) }}

select * from {{ source('raw', 'customers') }}

{% endsnapshot %}

dbt snapshot চালালে — প্রতি new updated_at-এ history row যোগ। Reporting-এ historical join সম্ভব।

৮ · dbt Core বনাম dbt Cloud

  • dbt Core (free, open-source):
    • CLI tool: dbt run, dbt test, dbt docs serve।
    • আপনি hosting (Airflow, GitHub Actions) ব্যবস্থা।
    • সব feature available।
    • Bangladesh-এ ছোট team, cost-conscious — এটাই বেছে নেয়।
  • dbt Cloud (paid SaaS, $100+/dev/month):
    • Browser-based IDE।
    • Managed scheduler — Airflow আলাদাভাবে দরকার নেই।
    • CI/CD built-in (PR check, slim CI)।
    • Hosted docs site, lineage UI।
    • Bangladesh-এ বড় enterprise (BRAC bank, Robi)-এ আসছে।
Bangladesh recommendation: শুরুতে dbt Core + Airflow + Snowflake/BigQuery free tier। Team ৫+ হলে dbt Cloud-এর developer plan। ROI ভাল — engineer-এর সময় বাঁচে।

৯ · একটি dbt project কেমন দেখায়

file structure
daraz_dwh/
├── dbt_project.yml          # project-level config
├── profiles.yml             # warehouse credentials (gitignored)
├── packages.yml             # dbt-utils, dbt-expectations
├── models/
│   ├── staging/
│   │   ├── _sources.yml
│   │   ├── stg_orders.sql
│   │   ├── stg_customers.sql
│   │   └── stg_products.sql
│   ├── intermediate/
│   │   └── int_orders_with_customer.sql
│   └── marts/
│       ├── _marts.yml       # tests, docs
│       ├── fct_orders.sql
│       ├── dim_customers.sql
│       └── customer_summary.sql
├── snapshots/
│   └── customers_snapshot.sql
├── tests/                   # custom SQL tests
│   └── assert_revenue_positive.sql
├── seeds/                   # static CSV → table
│   └── country_codes.csv
└── macros/                  # reusable Jinja
    └── generate_schema_name.sql

# Run commands:
dbt deps          # install packages
dbt run           # build all models
dbt test          # run all tests
dbt docs generate # build docs
dbt docs serve    # local docs site at :8080

    
Standard layout। dbt run dependency order-এ models build। Failed model-এর downstream skip। ১,০০০+ model-ও dbt smooth সামলায়।
profiles.yml warehouse credentials — কখনো Git-এ commit করবেন না। ~/.dbt/profiles.yml-এ রাখুন বা environment variable: password: "{{ env_var('DBT_PASSWORD') }}"।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ "ELT" নাকি "ETL" — dbt কোনটিকে enable করে এবং কেন? Bangladesh-এর context-এ এই shift practical কতটুকু?

dbt স্পষ্টভাবেই ELT-এর tool। বুঝতে হলে আগে তিনটি step-এর order বুঝে নিন।

ETL (পুরাতন paradigm, ১৯৯০-২০১০):

  • Extract: source থেকে data বের করুন।
  • Transform: আলাদা compute server (Informatica, Talend)-এ transform।
  • Load: warehouse-এ load।
  • Warehouse expensive ছিল — minimal data রাখা।

ELT (modern, ২০১৫+):

  • Extract: source থেকে।
  • Load: raw data সরাসরি warehouse-এ।
  • Transform: warehouse-এর ভেতরেই SQL দিয়ে।
  • Cloud warehouse (Snowflake, BigQuery) cheap ও fast — raw রাখা সম্ভব।

কেন ELT জিতছে:

  • Cloud economics: Snowflake/BigQuery storage cents/GB। compute decoupled।
  • Schema-on-read: raw রাখলে — পরে নতুন transformation possible original থেকে।
  • SQL ubiquity: আরও বেশি লোক SQL জানে — Python/Spark কম।
  • Vendor offload: Fivetran, Airbyte EL handle; dbt T।

dbt-এর ভূমিকা:

  • "T" layer-এর tool।
  • Raw warehouse-এ থাকা মানে — dbt-এর কাজের জায়গা warehouse।
  • Spark/EMR-এর জটিল compute layer extra।

Bangladesh-এর reality:

  • Cost concern: Snowflake credit-এ ১০ জন অ্যানালিস্টের warehouse — মাসে $২,০০০-৫,০০০। ছোট startup-এর জন্য বেশি।
  • Alternative: ClickHouse self-hosted, BigQuery free tier ($৩০০ credit), Postgres warehouse pattern।
  • EL tool: Fivetran expensive। Airbyte open-source — popular bd-তে। Custom Python script-ও common।
  • Bandwidth: বড় data extract slow connection-এ — incremental EL critical।
  • Skill gap: dbt শিখতে শুধু SQL + Git। এটি বড় advantage।

Practical scenarios:

  • BRAC Bank reporting: ELT excellent — সব transaction Snowflake/Synapse-এ load, dbt-এ analytics।
  • bKash fraud detection: ELT কাজে আসে না — sub-second latency দরকার, streaming ETL।
  • Daraz BI dashboard: ELT perfect — overnight refresh যথেষ্ট।
  • BTRC compliance reports: ELT — historical data যেমন ছিল accurately preserve।

Hybrid pattern:

  • "ETLT" — light transform (PII masking, format) load-এর সময়; deep transform warehouse-এ।
  • Operational systems-এ heavy compute না করতে।

মূল কথা: Cloud warehouse + dbt = analytics future। Bangladesh-এর enterprise এই path-এ আসছে। কিন্তু streaming ও ML inference আলাদা stack — ELT সবকিছুর সমাধান নয়।

প্র ০২ "Materialization" choice কঠিন। ১০০ GB transactions table-এ কখন view, কখন table, কখন incremental? ভুল choice-এর consequence কী?

Materialization decision = cost vs latency vs complexity trade-off। প্রতিটি model-এ আলাদা।

View — কখন:

  • Source table ছোট (১ GB-র নিচে)।
  • Query infrequent (প্রতিদিন ১-১০ বার)।
  • Underlying data fresh চাই।
  • Storage cost-sensitive।

View — সমস্যা ১০০ GB-তে:

  • প্রতি query-তে ১০০ GB scan — Snowflake-এ ~$৫।
  • BI tool ২০ user × ৫ refresh × $৫ = $৫০০/দিন!
  • Slow — query-time-এ aggregation চলে।

Table — কখন:

  • Read-heavy (BI dashboard, API)।
  • Source ছোট-মাঝারি (১০ GB-র নিচে rebuild affordable)।
  • Result-এ heavy aggregation/join — pre-compute।

Table — সমস্যা ১০০ GB-তে:

  • প্রতি dbt run-এ ১০০ GB rebuild — ৩০ মিনিট, $৫০।
  • প্রতিদিন rebuild = $১,৫০০/মাস শুধু এই model-এ।
  • Run window দীর্ঘ — downstream wait।

Incremental — ১০০ GB-তে আদর্শ:

  • প্রতি run-এ শুধু গতকালের ~৫০ MB process।
  • Run time ৩০ সেকেন্ড, cost cents।
  • Read fast (table-এর মতো)।
  • Trade-off: complexity বেশি, late-arriving data risk।

Decision tree (১০০ GB transaction table):

  • "Query কতবার?" — দিনে ১০+: not view।
  • "Source কত বড়?" — ১০ GB-র বেশি: not pure table।
  • "New row append-only?" — হ্যাঁ: incremental।
  • "Update history?" — হ্যাঁ: snapshot বা full refresh weekly।

Recommended pattern (bKash transactions):

# staging: view (cheap, transparent)
stg_transactions: {{ config(materialized='view') }}

# fact: incremental (huge, append-mostly)
fct_transactions: {{ config(
  materialized='incremental',
  unique_key='transaction_id',
  partition_by={'field': 'date', 'data_type': 'date'},
  cluster_by=['user_id']
) }}

# summary: table (small, queried often)
daily_revenue_summary: {{ config(materialized='table') }}

ভুল choice consequences:

  • View on huge: warehouse bill explosion। Query timeout। Dashboard load slow।
  • Table on huge: long run time, schedule miss। Late refresh = stale dashboards।
  • Incremental wrong key: duplicate rows, missing data।
  • Table on small read-once: wasted storage, complexity।

Late-arriving data trap:

  • Incremental-এ WHERE date >= max(date) = older arriving row miss।
  • Solution: lookback window (max - 3 days) + delete-insert।
  • Or weekly full_refresh: dbt run --full-refresh -s fct_transactions।

মূল কথা: Materialization = engineering decision, business decision নয়। Volume measure, query pattern monitor, ব্যয় track — তারপর choose।

প্র ০৩ আপনার dbt project-এ ৫০০ model। Junior engineer একটা model পরিবর্তন করতে চায়। কোনগুলো affected হবে? কী CI/CD safeguard লাগে?

এটি real production scenario। dbt-এর lineage capability এখানে ভাল কাজে।

Affected detection:

# downstream impact
dbt list --select +my_model+
# (+ before = upstream, + after = downstream)

# শুধু changed model + downstream
dbt build --select state:modified+ \
  --state ./prod-manifest

# Slim CI (PR-এ শুধু changed পরীক্ষা)
dbt build --select state:modified+ \
  --defer --state ./prod-manifest

State comparison:

  • state:modified — current vs main branch-এ যা ভিন্ন।
  • state:new — main-এ নেই।
  • "manifest.json" আগের build-এর — compare-এর basis।

CI/CD pipeline (GitHub Actions / GitLab):

name: dbt CI
on: pull_request

jobs:
  ci:
    steps:
      - uses: actions/checkout@v3
      - run: dbt deps
      - run: dbt seed --target ci
      - run: dbt build --select state:modified+ \
                --defer --state ./prod-manifest \
                --target ci
      - run: dbt source freshness

Safeguard layers:

  • Layer 1 — Static: SQL linter (sqlfluff), naming convention check।
  • Layer 2 — Compile: dbt parse — Jinja syntax error catch।
  • Layer 3 — Build: changed model + downstream actually build in CI schema।
  • Layer 4 — Test: not_null, unique, custom assertion।
  • Layer 5 — Compare: dbt-audit-helper দিয়ে old vs new diff।
  • Layer 6 — Approval: senior reviewer approve।
  • Layer 7 — Deploy: merge → production run।

Defer pattern:

  • CI-তে শুধু changed model build।
  • Upstream reference — production manifest থেকে।
  • ৫০০ model-এর pretty কম resource।
  • 10x+ faster CI।

Documentation enforcement:

  • dbt-checkpoint hook: undocumented column reject।
  • required_docs: true — model description ছাড়া fail।

Test coverage:

  • Primary key column-এ unique + not_null mandatory।
  • Foreign key column-এ relationships test।
  • Business rule (revenue > 0, status in valid list) custom test।

Production deployment:

  • Blue-green: parallel schema build, atomic rename।
  • Or zero-copy clone (Snowflake): CREATE TABLE x_new CLONE x।
  • Rollback plan: previous run-এর schema retain (১ দিন)।

Junior onboarding patterns:

  • Sandbox schema প্রতি dev-এর: dbt_alice, dbt_bob।
  • Pull-request template — checklist (test added? doc updated? lineage checked?)।
  • Pair programming — first ৩-৫ PR senior-এর সাথে।

Bangladesh enterprise context:

  • Audit log requirement (BB compliance) — সব schema change tracked।
  • Data lineage — dbt docs generated diagram regulator-কে দেখানো।
  • PII handling — dbt-snowflake-এ row access policy + dbt macro।

মূল কথা: dbt + Git + CI/CD = software engineering rigor for analytics। ৫০০ model managing-এর জন্য এই disciplines essential — না হলে production pipeline বছরের পর বছর fragile।

প্র ০৪ "Macros" dbt-এ Jinja-ভিত্তিক reusable code। কোন situation-এ macro লেখা proper? Bangladesh fintech-এর জন্য একটা useful macro design করুন।

Macro = Jinja template যা compile-time-এ SQL generate করে। C-এর preprocessor-এর মতো।

কখন macro proper:

  • DRY principle: একই SQL snippet ৩+ model-এ repeat।
  • Database-specific abstraction: Snowflake vs BigQuery syntax difference hide।
  • Dynamic generation: runtime input (variable) থেকে SQL।
  • Custom test: built-in test যথেষ্ট না।
  • Project conventions: schema naming, table aliasing standard।

কখন macro নয়:

  • একবার ব্যবহৃত SQL — over-engineering।
  • Business logic — model-এ থাকা ভাল (visible)।
  • Data-dependent logic — macro static, runtime data নয়।

Bangladesh fintech macro 1: BDT formatting

{% macro format_bdt(amount_col) %}
  case
    when {{ amount_col }} >= 10000000 then
      concat(round({{ amount_col }} / 10000000.0, 2), ' কোটি')
    when {{ amount_col }} >= 100000 then
      concat(round({{ amount_col }} / 100000.0, 2), ' লাখ')
    when {{ amount_col }} >= 1000 then
      concat(round({{ amount_col }} / 1000.0, 2), ' হাজার')
    else cast({{ amount_col }} as varchar)
  end
{% endmacro %}

-- Usage:
select {{ format_bdt('total_revenue') }} as revenue_display
from fct_revenue

Bangladesh fintech macro 2: NID validation

{% macro is_valid_nid(nid_col) %}
  -- Bangladesh NID: 10, 13, or 17 digits
  ({{ nid_col }} ~ '^[0-9]{10}$' or
   {{ nid_col }} ~ '^[0-9]{13}$' or
   {{ nid_col }} ~ '^[0-9]{17}$')
{% endmacro %}

-- Used in test:
{% test valid_nid(model, column_name) %}
  select * from {{ model }}
  where not {{ is_valid_nid(column_name) }}
    and {{ column_name }} is not null
{% endtest %}

Bangladesh fintech macro 3: PII masking

{% macro mask_phone(phone_col) %}
  case
    when current_role() in ('analyst', 'admin') then {{ phone_col }}
    else concat(left({{ phone_col }}, 5), 'XXXXXX')
  end
{% endmacro %}

-- Bangladesh phone: 01XXXXXXXXX (11 digits)
-- Show: 01700XXXXXX

Bangladesh fintech macro 4: Fiscal year

{% macro fiscal_year(date_col) %}
  case
    when extract(month from {{ date_col }}) >= 7
    then concat(extract(year from {{ date_col }}), '-',
                extract(year from {{ date_col }}) + 1)
    else concat(extract(year from {{ date_col }}) - 1, '-',
                extract(year from {{ date_col }}))
  end
{% endmacro %}

-- Bangladesh fiscal year: July-June
-- 2026-05 → "2025-2026"
-- 2026-08 → "2026-2027"

Bangladesh fintech macro 5: Mobile operator detection

{% macro mobile_operator(phone_col) %}
  case
    when {{ phone_col }} like '0170%' or {{ phone_col }} like '0171%' or
         {{ phone_col }} like '0172%' or {{ phone_col }} like '0173%' or
         {{ phone_col }} like '0174%' or {{ phone_col }} like '0175%' or
         {{ phone_col }} like '0176%' or {{ phone_col }} like '0177%' or
         {{ phone_col }} like '0178%' or {{ phone_col }} like '0179%' then 'Grameenphone'
    when {{ phone_col }} like '0181%' or {{ phone_col }} like '0182%' or
         {{ phone_col }} like '0183%' or {{ phone_col }} like '0184%' or
         {{ phone_col }} like '0185%' or {{ phone_col }} like '0186%' or
         {{ phone_col }} like '0187%' or {{ phone_col }} like '0188%' or
         {{ phone_col }} like '0189%' then 'Robi'
    when {{ phone_col }} like '019%' then 'Banglalink'
    when {{ phone_col }} like '015%' then 'Teletalk'
    when {{ phone_col }} like '013%' then 'Grameenphone'  -- newer
    else 'Unknown'
  end
{% endmacro %}

Best practices:

  • Macro file macros/ ফোল্ডারে।
  • Naming: verb-based (format_bdt, is_valid_nid)।
  • Document with docstring।
  • Test macro itself (yes, possible)।
  • Package candidate: reusable macros → dbt package publish।

Public packages worth knowing:

  • dbt-utils: generate_series, surrogate_key, pivot — daily use।
  • dbt-expectations: Great Expectations port — advanced data tests।
  • dbt-codegen: auto-generate model SQL from source schema।

মূল কথা: Macro = power tool — sparingly use। Business logic visible-এ থাকুক, plumbing macro-এ। Bangladesh-এর domain-specific macro অসাধারণ contribution হতে পারে।

অনুশীলন

  1. লিখুন: bKash-এর daily transaction summary model — date, total_count, total_amount, unique_user_count। stg_transactions ref করে।
    -- models/marts/daily_transaction_summary.sql
    {{ config(materialized='incremental', unique_key='transaction_date') }}
    
    select
        date(transaction_at)              as transaction_date,
        count(*)                          as total_count,
        sum(amount)                       as total_amount_bdt,
        count(distinct user_id)           as unique_user_count,
        avg(amount)                       as avg_amount_bdt
    from {{ ref('stg_transactions') }}
    where status = 'success'
    {% if is_incremental() %}
      and transaction_at >= (select coalesce(max(transaction_date), '1900-01-01') from {{ this }})
    {% endif %}
    group by 1

    YAML test:

    columns:
      - name: transaction_date
        tests: [unique, not_null]
      - name: total_amount_bdt
        tests:
          - dbt_utils.accepted_range:
              min_value: 0
  2. Choose materialization: তিন model-এর জন্য কোনটি?
    • (ক) stg_users — ১০ মিলিয়ন row, ৫ GB, daily refresh।
    • (খ) fct_clickstream — ৫০০ মিলিয়ন event/day, append-only।
    • (গ) top_10_products — daily small aggregation, dashboard।
    • (ক) view বা table — ৫ GB rebuild affordable। Read frequency দেখে decide।
    • (খ) incremental — append-only fact, huge volume। partition_by date।
    • (গ) table — ছোট, dashboard-এ frequent read, fast pre-compute।
  3. Test design: Daraz-এর fct_orders-এ কী কী test যোগ করবেন?
    columns:
      - name: order_id
        tests: [unique, not_null]
      - name: customer_id
        tests:
          - not_null
          - relationships:
              to: ref('dim_customers')
              field: customer_id
      - name: order_status
        tests:
          - accepted_values:
              values: ['pending', 'confirmed', 'shipped', 'delivered', 'cancelled']
      - name: amount_bdt
        tests:
          - not_null
          - dbt_utils.accepted_range:
              min_value: 0
              max_value: 10000000  # 1 কোটি cap
      - name: order_date
        tests:
          - not_null
          - dbt_utils.expression_is_true:
              expression: "<= current_date"

    Custom SQL test (tests/ folder):

    -- tests/assert_no_future_orders.sql
    select * from {{ ref('fct_orders') }}
    where order_date > current_date

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

dbt চেষ্টা করতে চান? pip install dbt-postgres বা dbt-bigquery দিয়ে শুরু করুন। অথবা Google Colab-এ DuckDB + dbt-duckdb adapter দিয়ে full project local-এ চালানো যায়।
পূর্ববর্তী পাঠ
পাঠ ১৪ · DAG লেখা ও schedule