PostgreSQL ofrece un soporte muy potente para almacenar y consultar datos JSON con sus tipos de datos json y jsonb. El tipo jsonb es especialmente útil porque guarda los datos en formato binario, lo que permite indexar y consultar de forma más eficiente que con el tipo json, basado en texto.
En este tutorial vamos a ver las operaciones JSON más comunes en PostgreSQL con ejemplos prácticos que puedes usar en tus aplicaciones.
Crear datos JSON
Empecemos creando una tabla con una columna jsonb e insertando algunos datos de ejemplo:
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
info JSONB
);
-- Insert a customer with basic information
INSERT INTO customers (info) VALUES (
'{"name": "Alice Smith", "email": "alice@example.com", "age": 32, "addresses": [
{"type": "home", "city": "San Francisco", "state": "CA"},
{"type": "work", "city": "Oakland", "state": "CA"}
]}'
);
-- Insert another customer
INSERT INTO customers (info) VALUES (
'{"name": "Bob Johnson", "email": "bob@example.com", "age": 45, "addresses": [
{"type": "home", "city": "Seattle", "state": "WA"}
], "orders": [101, 102, 103]}'
);
Operaciones JSON básicas
Obtener el documento JSON completo
SELECT info FROM customers WHERE id = 1;
Resultado:
{
"age": 32,
"name": "Alice Smith",
"email": "alice@example.com",
"addresses": [
{"city": "San Francisco", "type": "home", "state": "CA"},
{"city": "Oakland", "type": "work", "state": "CA"}
]
}
Extraer valores de JSON
PostgreSQL ofrece varios operadores para extraer valores de JSON:
Extraer un campo específico con el operador -> (devuelve JSON)
-- Get the name field as JSON
SELECT info -> 'name' FROM customers;
Resultado:
"Alice Smith"
"Bob Johnson"
Extraer un campo específico con el operador -» (devuelve texto)
-- Get the name field as text
SELECT info ->> 'name' AS customer_name FROM customers;
Resultado:
customer_name
----------------
Alice Smith
Bob Johnson
Acceder a objetos anidados
-- Getting the first address (array element 0) city
SELECT info -> 'addresses' -> 0 ->> 'city' AS home_city FROM customers;
Resultado:
home_city
------------
San Francisco
Seattle
Usar el operador #> para acceder por ruta
-- Using the path syntax to get the same value
SELECT info #> '{addresses,0,city}' FROM customers;
Resultado:
"San Francisco"
"Seattle"
Usar el operador #» para acceder por ruta como texto
-- Using the path syntax to get the same value as text
SELECT info #>> '{addresses,0,city}' AS home_city FROM customers;
Resultado:
home_city
------------
San Francisco
Seattle
Modificar datos JSON
PostgreSQL ofrece varias funciones para modificar datos JSON:
Agregar un campo con jsonb_set
-- Add a new field 'premium_customer' to a customer
UPDATE customers
SET info = jsonb_set(info, '{premium_customer}', 'true')
WHERE id = 1;
Eliminar un campo con el operador -
-- Remove the premium_customer field
UPDATE customers
SET info = info - 'premium_customer'
WHERE id = 1;
Combinar objetos JSON con ||
-- Add multiple fields by merging objects
UPDATE customers
SET info = info || '{"status": "active", "last_login": "2025-03-23"}'::jsonb
WHERE id = 1;
Actualizar un campo anidado
-- Update a nested field (home address state)
UPDATE customers
SET info = jsonb_set(
info,
'{addresses,0,state}',
'"NY"'::jsonb
)
WHERE id = 1;
Consultar arrays JSON
Trabajar con arrays JSON es una operación común:
Obtener la longitud de un array
-- Get the number of addresses for each customer
SELECT
info ->> 'name' AS customer_name,
jsonb_array_length(info -> 'addresses') AS address_count
FROM customers;
Resultado:
customer_name | address_count
---------------+--------------
Alice Smith | 2
Bob Johnson | 1
Extraer elementos específicos de un array
-- Get the second address (index 1) for Alice
SELECT info -> 'addresses' -> 1 FROM customers WHERE id = 1;
Resultado:
{"city": "Oakland", "type": "work", "state": "CA"}
Desanidar arrays para consultar elementos individuales
-- Unnest the addresses array to query individual addresses
SELECT
c.id,
c.info ->> 'name' AS customer_name,
jsonb_array_elements(c.info -> 'addresses') AS address
FROM customers c;
Resultado:
id | customer_name | address
---+---------------+------------------------------------------------------
1 | Alice Smith | {"city": "San Francisco", "type": "home", "state": "NY"}
1 | Alice Smith | {"city": "Oakland", "type": "work", "state": "CA"}
2 | Bob Johnson | {"city": "Seattle", "type": "home", "state": "WA"}
Filtrar con arrays desanidados
-- Find customers with addresses in CA
SELECT DISTINCT
c.id,
c.info ->> 'name' AS customer_name
FROM customers c,
jsonb_array_elements(c.info -> 'addresses') AS addr
WHERE addr ->> 'state' = 'CA';
Resultado:
id | customer_name
---+--------------
1 | Alice Smith
Verificar claves y valores
Verificar si existe una clave
-- Find customers who have the 'orders' key
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info ? 'orders';
Resultado:
id | customer_name
---+--------------
2 | Bob Johnson
Buscar valores en arrays
-- Find customers with a specific order number
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info -> 'orders' ? '101';
Resultado:
id | customer_name
---+--------------
2 | Bob Johnson
Verificar si algún elemento del array coincide
-- Find customers who have an address in Oakland (using path operators)
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info @> '{"addresses": [{"city": "Oakland"}]}';
Resultado:
id | customer_name
---+--------------
1 | Alice Smith
Indexar datos JSON
En bases de datos de producción con grandes volúmenes de JSON, indexar bien es fundamental:
Crear un índice GIN para consultas de contención
-- Create a GIN index for faster containment operations
CREATE INDEX idx_customers_info ON customers USING GIN (info);
Crear un índice específico para un campo JSON
-- Create an index on the 'email' field for fast lookups
CREATE INDEX idx_customers_email ON customers ((info ->> 'email'));
Usar campos indexados en consultas
-- This query can use the index we created above
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info ->> 'email' = 'alice@example.com';
Convertir entre JSON y tablas
Convertir JSON en filas (jsonb_to_record)
-- Extract JSON fields into a tabular structure
SELECT
id,
rec.name,
rec.email,
rec.age
FROM
customers,
jsonb_to_record(info) AS rec(
name text,
email text,
age int
);
Resultado:
id | name | email | age
---+--------------+-------------------+-----
1 | Alice Smith | alice@example.com | 32
2 | Bob Johnson | bob@example.com | 45
Convertir arrays anidados en tablas (jsonb_to_recordset)
-- Extract addresses from all customers into a table
SELECT
c.id,
c.info ->> 'name' AS customer_name,
addr.type AS address_type,
addr.city,
addr.state
FROM
customers c,
jsonb_to_recordset(c.info -> 'addresses') AS addr(
type text,
city text,
state text
);
Resultado:
id | customer_name | address_type | city | state
---+---------------+--------------+---------------+------
1 | Alice Smith | home | San Francisco | NY
1 | Alice Smith | work | Oakland | CA
2 | Bob Johnson | home | Seattle | WA
Convertir resultados de consultas a JSON
-- Aggregate customer orders into a JSON array
SELECT
c.id,
c.info ->> 'name' AS customer_name,
json_agg(o.order_id) AS orders
FROM
customers c
CROSS JOIN LATERAL (
SELECT jsonb_array_elements_text(c.info -> 'orders') AS order_id
) o
GROUP BY c.id, c.info ->> 'name';
Resultado:
id | customer_name | orders
---+---------------+----------
2 | Bob Johnson | ["101", "102", "103"]
Consideraciones de rendimiento
Cuando trabajes con JSON en PostgreSQL, ten en cuenta estas consideraciones de rendimiento:
-
Usa
jsonben lugar dejson: el tipojsonbofrece mejor rendimiento en la mayoría de las operaciones. -
Indexa con estrategia: usa índices GIN para consultas de contención, pero crea índices específicos para los campos que consultas con frecuencia.
-
Piensa bien la desnormalización: aunque JSON te permite desnormalizar tu modelo de datos, anidar en exceso puede afectar el rendimiento de las consultas.
-
Vigila tus patrones de consulta: las expresiones de ruta JSON complejas o desanidar arrays grandes con frecuencia pueden ser operaciones costosas.
-
Limita el tamaño de los arrays: los arrays JSON muy grandes pueden causar problemas de rendimiento al desanidarlos.
-
Usa proyección: extrae solo los campos que necesitas en lugar de devolver el documento JSON completo.
Conclusión
Las capacidades JSON de PostgreSQL lo convierten en una excelente opción para aplicaciones que necesitan almacenar y consultar datos semiestructurados. Con los operadores y funciones de este tutorial, puedes trabajar con datos JSON de forma eficaz sin dejar de aprovechar la potencia y confiabilidad de una base de datos relacional madura.
Para modelos de datos complejos, considera un enfoque híbrido: usa tablas estructuradas para esquemas fijos y datos que consultas con frecuencia, y usa JSON para datos flexibles sin esquema o estructuras anidadas que serían difíciles de modelar de forma relacional.
¿Listo para probar estos ejemplos tú mismo? Abre Beekeeper Studio y empieza hoy a explorar las capacidades JSON de PostgreSQL.
Beekeeper Studio es un cliente de bases de datos gratuito y de código abierto
La mejor herramienta de consultas y edición de SQL que he usado. Tiene todo lo que necesito para administrar mi base de datos. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio es rápido, intuitivo y fácil de usar. Es compatible con muchísimas bases de datos y funciona de maravilla en Windows, Mac y Linux.
Lo que dicen los usuarios de Beekeeper Studio
"Beekeeper Studio reemplazó por completo mi antiguo flujo de trabajo con SQL. Es rápido, intuitivo y hace que trabajar con bases de datos vuelva a ser agradable."
"He probado muchas GUI de bases de datos, pero Beekeeper logra el equilibrio perfecto entre funciones y simplicidad. Simplemente funciona."