Salta al contenuto principale

02 - Setup dbt Core: Struttura Progetto, Profili di Connessione e Prima Trasformazione SQL

dbt 14 min Setup dbt Core: Struttura Progetto, Profili di Connessione e Prima Trasformazione SQL Dalla riga di comando alla prima trasformazione:...

Cosa ne pensi di questo articolo?

02 - Setup dbt Core: Struttura Progetto, Profili di Connessione e Prima Trasformazione SQL
dbtFoto:Brett SaylesPexels

In Breve

dbt 14 min Setup dbt Core: Struttura Progetto, Profili di Connessione e Prima Trasformazione SQL Dalla riga di comando alla prima trasformazione:...

dbt 14 min

Setup dbt Core: Struttura Progetto, Profili di Connessione e Prima Trasformazione SQL

Dalla riga di comando alla prima trasformazione: configura dbt Core con profiles.yml per BigQuery, Snowflake o PostgreSQL, esplora la struttura del progetto (models, tests, seeds, macros) e scopri il macro ref() che crea dipendenze automatiche tra modelli.

dbt init dbt profiles dbt run dbt_project.yml ref macro

Prerequisiti e Installazione

dbt Core richiede Python 3.8+ e si installa via pip. La cosa importante è installare non solo il pacchetto base ma anche l'adapter specifico per il tuo warehouse. Ogni warehouse ha il suo adapter, perché i dialetti SQL differiscono.

# Installa dbt Core con l'adapter per PostgreSQL (ottimo per iniziare in locale)
pip install dbt-postgres

# Per BigQuery
pip install dbt-bigquery

# Per Snowflake
pip install dbt-snowflake

# Per DuckDB (ideale per sviluppo locale senza infrastruttura)
pip install dbt-duckdb

# Verifica l'installazione
dbt --version
# Core:
#   - installed: 1.9.0
#   - latest:    1.9.0 - Up to date!

Inizia con DuckDB per lo Sviluppo Locale

Se non hai accesso immediato a BigQuery o Snowflake, DuckDB è il modo più rapido per iniziare: è un database embedded che gira direttamente in locale, non richiede infrastruttura e dbt-duckdb funziona su qualsiasi macchina. Perfetto per imparare senza costi.

Creare il Primo Progetto dbt

Il comando dbt init crea la struttura di cartelle di un progetto dbt e ti guida nella configurazione iniziale:

dbt init jaffle_shop

# dbt chiederà:
# 1. Quale database vuoi usare? (postgres/bigquery/snowflake/...)
# 2. [Per postgres] host, port, user, password, database, schema

# La struttura creata:
jaffle_shop/
├── dbt_project.yml          # configurazione principale
├── README.md
├── analyses/               # query ad hoc (non materializzate)
├── macros/                 # funzioni Jinja riutilizzabili
├── models/
│   └── example/            # modelli di esempio (da eliminare)
├── seeds/                  # CSV statici
├── snapshots/              # snapshot per SCD
└── tests/                  # test SQL singolari

Il File dbt_project.yml

Il cuore della configurazione del progetto è dbt_project.yml. Defini qui il nome del progetto, la versione di dbt, i percorsi delle directory e la configurazione globale dei modelli:

# dbt_project.yml
name: 'jaffle_shop'
version: '1.0.0'

# Versione minima di dbt richiesta
require-dbt-version: ">=1.8.0"

# Percorso del profile da usare (in profiles.yml)
profile: 'jaffle_shop'

# Directory dei modelli
model-paths: ["models"]
analysis-paths: ["analyses"]
test-paths: ["tests"]
seed-paths: ["seeds"]
macro-paths: ["macros"]
snapshot-paths: ["snapshots"]

# Dove salvare i log e i target compilati
target-path: "target"
log-path: "logs"
clean-targets: ["target", "dbt_packages"]

# Configurazione dei modelli per directory
models:
  jaffle_shop:
    # Tutti i modelli del progetto sono view per default
    +materialized: view

    staging:
      # I modelli in staging/ sono sempre view
      +materialized: view
      +schema: staging          # savedano in schema 'staging'

    marts:
      +materialized: table      # I marts sono table per performance
      +schema: marts

Il File profiles.yml

Le credenziali di connessione vanno in ~/.dbt/profiles.yml (nella home directory, mai nel repository). Ogni profilo può avere più target (dev, staging, prod):

# ~/.dbt/profiles.yml
jaffle_shop:
  target: dev                    # target di default

  outputs:
    dev:
      type: postgres
      host: localhost
      port: 5432
      user: "{{ env_var('DB_USER') }}"      # usa variabili d'ambiente
      password: "{{ env_var('DB_PASSWORD') }}"
      dbname: jaffle_shop_dev
      schema: dbt_dev_federico   # schema personale per sviluppo
      threads: 4

    prod:
      type: postgres
      host: "{{ env_var('PROD_DB_HOST') }}"
      port: 5432
      user: "{{ env_var('PROD_DB_USER') }}"
      password: "{{ env_var('PROD_DB_PASSWORD') }}"
      dbname: jaffle_shop_prod
      schema: dbt_prod
      threads: 8

La convenzione è che ogni sviluppatore abbia uno schema personale (es. dbt_dev_federico) per evitare conflitti durante lo sviluppo in parallelo.

Modelli dbt: La Unit Fondamentale

Un modello dbt è un file .sql nella directory models/. Il contenuto è un semplice SELECT - dbt si occupa di creare la view o la table corrispondente nel warehouse.

Iniziamo con un esempio realistico. Supponiamo di avere una tabella raw.orders nel warehouse con dati grezzi provenienti dall'applicazione:

-- models/staging/stg_orders.sql
-- Staging model: rinomina, casta, pulisce - nessuna logica di business

WITH source AS (
    SELECT * FROM {{ source('raw', 'orders') }}   -- 'source' punta alla sorgente raw
),

renamed AS (
    SELECT
        id                                         AS order_id,
        user_id                                    AS customer_id,
        order_date                                 AS created_at,
        status,
        CAST(amount AS DECIMAL(10,2))              AS total_amount,
        LOWER(payment_method)                      AS payment_method,
        _loaded_at                                 AS loaded_at    -- metadata pipeline
    FROM source
    WHERE id IS NOT NULL                           -- filtra record corrotti
)

SELECT * FROM renamed

Il Macro ref()

Il macro ref() è la feature più importante di dbt. Quando scrivi {{ ref('stg_orders') }}, dbt:

  1. Risolve il nome corretto della tabella/view nel warehouse per l'ambiente corrente
  2. Registra la dipendenza nel DAG (directed acyclic graph)
  3. Garantisce che il modello dipendente venga eseguito prima
-- models/marts/finance/orders_monthly.sql
-- Usa ref() per dipendere da stg_orders

WITH orders AS (
    SELECT * FROM {{ ref('stg_orders') }}          -- dbt risolve automaticamente lo schema
),

monthly_aggregated AS (
    SELECT
        DATE_TRUNC('month', created_at)            AS month,
        payment_method,
        COUNT(*)                                   AS order_count,
        SUM(total_amount)                          AS gross_revenue,
        AVG(total_amount)                          AS avg_order_value,
        COUNT(DISTINCT customer_id)                AS unique_customers
    FROM orders
    WHERE status = 'completed'
    GROUP BY 1, 2
)

SELECT * FROM monthly_aggregated

Il Macro source()

Per accedere alle sorgenti raw (tabelle non create da dbt), usa source() invece di ref(). Le sorgenti vanno dichiarate in un file sources.yml:

# models/staging/sources.yml
version: 2

sources:
  - name: raw                    # nome del source group
    database: raw_db             # database nel warehouse
    schema: public               # schema nel warehouse
    tables:
      - name: orders
        description: "Ordini grezzi dall'applicazione"
        loaded_at_field: _loaded_at    # campo per freshness check
        freshness:
          warn_after: {count: 12, period: hour}
          error_after: {count: 24, period: hour}
      - name: customers
        description: "Clienti grezzi dall'applicazione"

Con questa configurazione, puoi eseguire dbt source freshness per verificare che le sorgenti siano aggiornate prima di avviare le trasformazioni.

Eseguire dbt: i Comandi Fondamentali

# Esegui tutti i modelli (materialization nel warehouse)
dbt run

# Esegui solo i modelli staging
dbt run --select staging

# Esegui un singolo modello e tutte le sue dipendenze (+)
dbt run --select +orders_monthly

# Esegui tutti i test definiti nello schema YAML
dbt test

# Testa solo un modello specifico
dbt test --select stg_orders

# Compila i modelli senza eseguirli (utile per debug)
dbt compile

# Verifica freshness delle sorgenti
dbt source freshness

# Genera e serve la documentazione
dbt docs generate
dbt docs serve                  # apre http://localhost:8080

Struttura Raccomandata dei Modelli

La struttura a tre livelli raccomandata dalla comunità dbt:

models/
├── staging/                    # Layer 1: vicino alla sorgente
│   ├── sources.yml            # dichiarazione sorgenti
│   ├── schema.yml             # test + documentazione
│   ├── stg_orders.sql
│   ├── stg_customers.sql
│   └── stg_products.sql
├── intermediate/               # Layer 2: join complessi (opzionale)
│   ├── int_orders_enriched.sql # join ordini + clienti
└── marts/                      # Layer 3: pronti per consumo
    ├── finance/
    │   ├── schema.yml
    │   ├── orders_monthly.sql
    │   └── revenue_by_country.sql
    └── marketing/
        └── customer_cohorts.sql

Anti-pattern: Logica di Business nello Staging

I modelli di staging devono fare solo operazioni "stupide": rinominare colonne, castare tipi, deduplicare. La logica di business (calcoli, aggregazioni, join) va nei modelli intermediate o marts. Se il tuo staging model ha un GROUP BY o più di un paio di colonne calcolate, probabilmente stai facendo troppo in quel layer.

Verifica del Setup con dbt debug

Prima di eseguire dbt run, verifica che la connessione al warehouse funzioni:

dbt debug

# Output atteso:
# Configuration:
#   profiles.yml file [OK found and valid]
#   dbt_project.yml file [OK found and valid]
# Required dependencies:
#  - git [OK found]
# Connection:
#   host: localhost
#   port: 5432
#   user: federico
#   database: jaffle_shop_dev
#   schema: dbt_dev_federico
#   [OK connection ok]

Conclusioni e Prossimi Passi

Abbiamo configurato un progetto dbt funzionante con la struttura a tre livelli (staging → intermediate → marts), i profili di connessione per diversi ambienti, le sorgenti dichiarate con freshness check e i primi modelli con ref() e source().

Il passo successivo è rendere il nostro SQL dinamico con Jinja: variabili, loop, condizioni e macro riutilizzabili che eliminano la duplicazione di codice nel warehouse.

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti