(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisboa

Cómo aplanar JSON anidado en ELT: paso a paso

João Barros 08 de July de 2026 4 min de lectura

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, INSERT y CREATE 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?