data-engineering
dbt: La herramienta que cambió el Analytics Engineering
Por qué dbt se convirtió en el estándar de la industria y cómo empezar a usarlo.
5 de abril de 202610 min
dbtanalytics-engineeringsql
dbt (data build tool) transformó cómo los equipos de datos escriben SQL. Dos años después de adoptarlo en producción, acá el análisis honesto de qué funciona y qué no.
¿Por qué dbt?
Antes de dbt, nuestro SQL de transformación era un desastre:
- Scripts SQL en carpetas sin orden
- Sin tests para validar los datos
- Sin documentación sobre qué hace cada query
- Dependencias entre queries manejadas manualmente
dbt resuelve esto con un enfoque de software engineering aplicado al SQL.
La estructura básica
analytics/
├── dbt_project.yml
├── models/
│ ├── staging/ # Datos crudos limpiados
│ │ ├── stg_orders.sql
│ │ └── stg_sellers.sql
│ ├── intermediate/ # Joins y transformaciones
│ │ └── int_seller_orders.sql
│ └── marts/ # Tablas finales para BI
│ └── mart_seller_metrics.sql
├── tests/
└── macros/
Modelos con referencias
-- models/staging/stg_orders.sql
-- Limpia datos crudos de la tabla orders
SELECT
order_id,
TRIM(seller_id) as seller_id,
CAST(gmv AS NUMERIC) as gmv,
PARSE_TIMESTAMP("%Y-%m-%d %H:%M:%S", created_at) as created_at,
LOWER(status) as status
FROM {{ source("raw", "orders") }}
WHERE order_id IS NOT NULL
AND gmv > 0
-- models/marts/mart_seller_metrics.sql
-- Métricas diarias por seller
WITH orders AS (
SELECT * FROM {{ ref("stg_orders") }}
),
sellers AS (
SELECT * FROM {{ ref("stg_sellers") }}
)
SELECT
DATE(o.created_at) as metric_date,
s.seller_id,
s.country,
COUNT(*) as total_orders,
SUM(o.gmv) as total_gmv,
AVG(o.gmv) as avg_gmv
FROM orders o
JOIN sellers s USING (seller_id)
WHERE o.status = "completed"
GROUP BY 1, 2, 3
Tests: la killer feature
# models/staging/schema.yml
version: 2
models:
- name: stg_orders
columns:
- name: order_id
tests:
- unique
- not_null
- name: gmv
tests:
- not_null
- dbt_utils.accepted_range:
min_value: 0
max_value: 1000000
- name: status
tests:
- accepted_values:
values: ["pending", "completed", "cancelled"]
# Correr todos los tests
dbt test
# Output
Found 24 models, 89 tests
Completed successfully. Pass: 89, Warn: 0, Error: 0, Skip: 0
Materializations
-- Table: reconstruye la tabla completa cada run
{{ config(materialized="table") }}
-- View: no almacena datos, solo la query
{{ config(materialized="view") }}
-- Incremental: solo procesa registros nuevos
{{ config(
materialized="incremental",
unique_key="order_id",
partition_by={"field": "created_at", "data_type": "timestamp"}
) }}
SELECT * FROM {{ source("raw", "orders") }}
{% if is_incremental() %}
WHERE created_at > (SELECT MAX(created_at) FROM {{ this }})
{% endif %}
Lo que no funciona bien
- Debugging es difícil: Cuando un modelo falla, el error a veces está varios pasos upstream
- No es para lógica compleja: dbt es SQL. Si necesitás Python, se complica (dbt Python models existen pero son limitados)
- Compile time: Con 300+ modelos,
dbt compiletarda. Los CI/CD se sienten lentos
Veredicto
dbt es casi obligatorio para equipos de analytics de 3+ personas. El valor está en los tests y la documentación automática. La curva de aprendizaje es de 1-2 semanas para alguien con SQL sólido.
Escrito por Mariano Gobea Alcoba