General
¿Por qué dbt?
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 ...
21 de diciembre de 20253 min read
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