Cómo aplanar JSON anidado en ELT: paso a paso
JSON es el formato más común para recibir datos de APIs, aplicaciones y logs, pero rara vez llega listo para el análisis. En un enfoque ELT se carga el JSON tal como viene y solo después se transforma, ya dentro del data warehouse. La función OPENJSON de SQL Server permite aplanar JSON anidado y convertirlo en columnas relacionales listas para consultar, sin herramientas externas.
Requisitos previos
- SQL Server 2016 o superior, o Azure SQL Database (nivel de compatibilidad 130 o superior).
- Permisos para crear tablas y ejecutar consultas en una base de datos de staging.
- Un ejemplo de payload JSON (usamos uno sencillo a lo largo de la guía).
- Nociones básicas de T-SQL:
SELECT,INSERTyCREATE TABLE.
Paso 1: Cargar el JSON en bruto (la "L" de ELT)
En ELT, el primer paso es aterrizar los datos tal como llegan, sin modificarlos. Crea una tabla de staging con una columna NVARCHAR(MAX) para guardar el documento JSON completo, más una fecha de carga. Así preservas el origen y puedes reprocesar más tarde si cambian las reglas de negocio.
CREATE TABLE stg.PedidosRaw (
id_carga INT IDENTITY(1,1) PRIMARY KEY,
payload NVARCHAR(MAX) NOT NULL,
carregado_em DATETIME2 DEFAULT SYSUTCDATETIME()
);
INSERT INTO stg.PedidosRaw (payload)
VALUES (N'{
"pedido_id": 1024,
"cliente": { "nome": "Ana Dias", "pais": "PT" },
"linhas": [
{ "sku": "A-1", "qtd": 2, "preco": 9.90 },
{ "sku": "B-7", "qtd": 1, "preco": 19.50 }
]
}');
Paso 2: Leer los campos de nivel superior con OPENJSON
Antes de aplanar, confirma que el texto es JSON válido con ISJSON y lee los campos de primer nivel. OPENJSON con una cláusula WITH asigna rutas JSON a columnas con tipo — fíjate cómo $.cliente.nome llega a un valor anidado sin esfuerzo.
SELECT j.pedido_id, j.cliente_nome, j.cliente_pais
FROM stg.PedidosRaw AS r
CROSS APPLY OPENJSON(r.payload)
WITH (
pedido_id INT '$.pedido_id',
cliente_nome NVARCHAR(100) '$.cliente.nome',
cliente_pais NVARCHAR(2) '$.cliente.pais'
) AS j
WHERE ISJSON(r.payload) = 1;
Paso 3: Expandir el array anidado (una fila por elemento)
El array linhas tiene varios elementos. Para obtener una fila por elemento, aplica OPENJSON una segunda vez sobre el array. Encadenar CROSS APPLY une la cabecera del pedido con cada línea, aplanando la relación padre-hijo.
SELECT
cab.pedido_id,
cab.cliente_nome,
linha.sku,
linha.qtd,
linha.preco
FROM stg.PedidosRaw AS r
CROSS APPLY OPENJSON(r.payload)
WITH (
pedido_id INT '$.pedido_id',
cliente_nome NVARCHAR(100) '$.cliente.nome',
linhas NVARCHAR(MAX) '$.linhas' AS JSON
) AS cab
CROSS APPLY OPENJSON(cab.linhas)
WITH (
sku NVARCHAR(20) '$.sku',
qtd INT '$.qtd',
preco DECIMAL(10,2) '$.preco'
) AS linha;
La palabra clave AS JSON le indica a OPENJSON que linhas es un fragmento JSON, permitiendo abrirlo en el segundo OPENJSON. Sin ella — y sin que la columna sea NVARCHAR(MAX) — obtienes un error o valores nulos.
Paso 4: Materializar la capa limpia (la "T")
Por último, guarda el resultado aplanado en una tabla limpia (silver), para que los modelos posteriores consulten columnas en lugar de JSON. Un simple INSERT ... SELECT reutiliza la consulta del paso anterior.
CREATE TABLE clean.PedidoLinhas (
pedido_id INT,
cliente_nome NVARCHAR(100),
sku NVARCHAR(20),
qtd INT,
preco DECIMAL(10,2)
);
INSERT INTO clean.PedidoLinhas (pedido_id, cliente_nome, sku, qtd, preco)
SELECT cab.pedido_id, cab.cliente_nome, linha.sku, linha.qtd, linha.preco
FROM stg.PedidosRaw AS r
CROSS APPLY OPENJSON(r.payload)
WITH (
pedido_id INT '$.pedido_id',
cliente_nome NVARCHAR(100) '$.cliente.nome',
linhas NVARCHAR(MAX) '$.linhas' AS JSON
) AS cab
CROSS APPLY OPENJSON(cab.linhas)
WITH (
sku NVARCHAR(20) '$.sku',
qtd INT '$.qtd',
preco DECIMAL(10,2) '$.preco'
) AS linha;
Comprobar el resultado
Consulta la tabla limpia para confirmar el aplanado.
SELECT * FROM clean.PedidoLinhas;
Deberías obtener dos filas para el pedido 1024 — A-1 (qtd 2, precio 9.90) y B-7 (qtd 1, precio 19.50) — con qtd y preco ya como números, no texto. Si aparecen las dos filas con los tipos correctos, el aplanado funcionó. Un error común es olvidar AS JSON en el Paso 3: sin él, el array no se abre y la columna queda en NULL.
Conclusión
Con OPENJSON y CROSS APPLY pasas de un documento JSON anidado a una tabla relacional limpia en pocos pasos, manteniendo el patrón ELT: cargar primero, transformar después. Como siguiente paso, coloca esta lógica en un procedimiento almacenado y procesa solo las cargas nuevas para ahorrar tiempo. Y tú, ¿cuál es el JSON más complicado que has tenido que aplanar?