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

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