Hologres admite los tipos de datos semiestructurados JSON y JSONB para almacenar y consultar datos flexibles sin esquema. Este tema describe las diferencias entre ambos tipos, los operadores y funciones compatibles, la indexación GIN y la integración con Realtime Compute for Apache Flink.
JSON frente a JSONB: cómo elegir el tipo adecuado
Ambos tipos almacenan datos con formato JSON, pero difieren en su formato de almacenamiento y características de rendimiento.
|
JSON |
JSONB |
|
|
Formato de almacenamiento |
Texto sin formato |
Binario descompuesto |
|
Velocidad de escritura |
Rápida |
Más lenta (convierte datos adicionales durante la escritura) |
|
Velocidad de lectura |
Más lenta (vuelve a analizar los datos en cada consulta) |
Más rápida (no requiere volver a analizar los datos) |
|
Conserva el texto de entrada exacto |
Sí: conserva espacios, orden de claves y claves duplicadas |
No: elimina espacios, deduplica claves (prevalece el último valor) y no conserva el orden de las claves |
|
Admite índices GIN |
No |
Sí (Hologres V1.1+) |
|
Admite almacenamiento orientado a columnas |
No |
Sí (Hologres V1.3+) |
Recomendación: utilice JSONB para la mayoría de las cargas de trabajo. Las consultas sobre JSONB son más rápidas, admite índices GIN y es compatible con el almacenamiento orientado a columnas. Use JSON únicamente cuando necesite conservar el texto de entrada exacto, incluido el orden de las claves, los espacios y las claves duplicadas.
Comportamiento con claves duplicadas: cuando una misma clave aparece varias veces en un valor de entrada, JSON conserva todos los pares clave-valor (las funciones de procesamiento toman el último valor como válido), mientras que JSONB conserva solo el último valor.
Límites
|
Restricción |
Detalle |
|
Tipo de datos JSON |
Requiere Hologres V0.9 o posterior. |
|
Índices GIN para JSONB |
Requiere Hologres V1.1 o posterior. |
|
Almacenamiento orientado a columnas para JSONB |
Requiere Hologres V1.3 o posterior. Solo es compatible con tablas orientadas a columnas; no se admiten tablas orientadas a filas. El almacenamiento orientado a columnas se activa únicamente cuando la tabla tiene 1000 registros o más. |
|
Índices GIN + almacenamiento orientado a columnas |
Son mutuamente excluyentes a partir de Hologres V3.0.42 y V3.1.10. Si habilita ambas opciones, los índices GIN no surten efecto. Utilice el almacenamiento orientado a columnas para campos no dispersos. |
|
Funciones no compatibles |
|
Alternativa para json_extract_path y jsonb_extract_path: utilice el operador de ruta #>.
-- json_extract_path equivalent
SELECT '{"key":{"key1":"key1","key2":"key2"}}'::json #> '{"key","key1"}';
-- jsonb_extract_path equivalent
SELECT '{"key":{"key1":"key1","key2":"key2"}}'::jsonb #> '{"key","key1"}';
Para obtener más información sobre cómo actualizar su instancia, consulte Instance upgrades.
Operadores
Operadores de uso común
A continuación se describen seis operadores que funcionan tanto con JSON como con JSONB.
|
**Operador** |
**Tipo del operando derecho** |
**Descripción** |
**Ejemplo** |
**Resultado** |
|
|
int |
Obtiene un elemento de un arreglo JSON por índice (base 0; los valores negativos cuentan desde el final). |
|
|
|
|
text |
Obtiene un campo de un objeto JSON por clave. |
|
|
|
|
int |
Obtiene un elemento de un arreglo JSON como texto. |
|
|
|
|
text |
Obtiene un campo de un objeto JSON como texto. |
|
|
|
|
text[] |
Obtiene un objeto JSON en la ruta especificada. |
|
|
|
|
text[] |
Obtiene un objeto JSON en la ruta especificada como texto. |
|
|
Operadores exclusivos de JSONB
Los siguientes operadores aplican únicamente a datos JSONB. Permiten comprobar contención, verificar la existencia de claves, concatenar y eliminar elementos.
|
**Operador** |
**Tipo del operando derecho** |
**Descripción** |
**Ejemplo** |
**Resultado** |
||||
|
|
jsonb |
Comprueba si el valor izquierdo contiene la ruta o el valor JSON derecho. |
|
|
||||
|
|
jsonb |
Comprueba si la ruta o el valor JSON izquierdo está contenido en el valor derecho. |
|
|
||||
|
|
text |
Comprueba si existe una cadena de clave o de elemento en el valor JSONB. |
|
|
||||
|
|
` |
text[] |
Comprueba si alguna cadena de clave o de elemento del arreglo existe en el valor JSONB. |
|
array['b', 'c']` |
|
||
|
|
text[] |
Comprueba si todas las cadenas del arreglo existen en el valor JSONB. |
|
|
||||
|
|
` |
jsonb |
Concatena dos valores JSONB. En objetos con la misma clave, prevalece el valor del operando derecho. Este operador no es recursivo. |
|
'["c", "d"]'::jsonb` |
|
||
|
|
text |
Elimina la clave o el valor coincidente del operando izquierdo. |
|
|
||||
|
|
text[] |
Elimina varias claves o valores coincidentes del operando izquierdo. |
|
|
||||
|
|
integer |
Elimina el elemento del arreglo en la posición especificada (los valores negativos cuentan desde el final). Devuelve un error si el valor no es un arreglo. |
|
|
||||
|
|
text[] |
Elimina el elemento en la ruta especificada. En arreglos, los enteros negativos cuentan desde el final. |
|
|
Funciones
Funciones de procesamiento
|
**Función** |
**Tipo de retorno** |
**Descripción** |
**Ejemplo** |
**Resultado** |
||
|
|
int |
Devuelve el número de elementos del arreglo JSON más externo. |
|
|
||
|
|
setof text |
Devuelve el conjunto de claves del objeto JSON más externo. |
|
|
||
|
|
anyelement |
Expande el objeto en |
|
|
["2", "a b"] |
{"d": 4, "e": "a b c"}` |
|
|
setof anyelement |
Expande el arreglo de objetos más externo en |
|
|
2` |
4` |
|
|
setof json / setof jsonb |
Expande un arreglo JSON en un conjunto de valores JSON. |
|
|
||
|
|
setof text |
Expande un arreglo JSON en un conjunto de valores de texto. |
|
|
||
|
|
text |
Devuelve el tipo de datos del valor JSON más externo como texto. Valores posibles: |
|
|
||
|
|
json / jsonb |
Elimina todos los campos de objeto con valores nulos. Los demás valores nulos se conservan. |
|
|
||
|
|
jsonb |
Reemplaza el valor en |
|
|
||
|
|
jsonb |
Inserta |
|
|
||
|
|
text |
Devuelve |
|
|
||
|
|
jsonb |
Agrega valores (incluidos los nulos) en un arreglo JSON. |
Consulte el ejemplo siguiente. |
— |
||
|
|
jsonb |
Agrega pares clave-valor en un objeto JSON. Las claves no pueden ser nulas; los valores sí. |
Consulte el ejemplo siguiente. |
— |
||
|
|
boolean |
Devuelve |
Consulte el ejemplo siguiente. |
— |
Ejemplo de jsonb_agg y jsonb_object_agg:
DROP TABLE IF EXISTS t;
CREATE TABLE t (
k int PRIMARY KEY,
class int NOT NULL,
v text NOT NULL
);
INSERT INTO t (k, class, v)
SELECT
(1 + s.v),
CASE (s.v) < 3
WHEN TRUE THEN 1
ELSE 2
END,
chr(97 + s.v)
FROM generate_series(0, 5) AS s (v);
SELECT
class,
jsonb_agg(v ORDER BY v DESC) FILTER (WHERE v <> 'b') AS "jsonb_agg",
jsonb_object_agg(v, k ORDER BY v DESC) FILTER (WHERE v <> 'e') AS "jsonb_object_agg(v, k)"
FROM t
GROUP BY class;
Resultado:
class | jsonb_agg | jsonb_object_agg(v, k)
-------+-----------------+--------------------------
1 | ["c", "a"] | {"a": 1, "b": 2, "c": 3}
2 | ["f", "e", "d"] | {"d": 4, "f": 6}
Ejemplo de is_valid_json:
DROP TABLE IF EXISTS test_json;
CREATE TABLE test_json (
id int,
json_strings text
);
INSERT INTO test_json
VALUES (1, '{"a":2}'), (2, '{"a":{"b":{"c":1}}}'), (3, '{"a": [1,2,"b"]}');
INSERT INTO test_json
VALUES (4, '{{}}'), (5, '{1:"a"}'), (6, '[1,2,3]');
SELECT
id,
json_strings,
is_valid_json(json_strings)
FROM test_json
ORDER BY id;
Resultado:
id | json_strings | is_valid_json
----+---------------------+--------------
1 | {"a":2} | true
2 | {"a":{"b":{"c":1}}} | true
3 | {"a": [1,2,"b"]} | true
4 | {{}} | false
5 | {1:"a"} | false
6 | [1,2,3] | true
Funciones de análisis sintáctico
|
**Función** |
**Descripción** |
**Ejemplo** |
**Resultado** |
|
|
Convierte un valor de texto a JSONB. Devuelve null si la entrada no es JSONB válido. Requiere Hologres V2.0.24 o posterior. |
|
|
|
|
Devuelve el valor como un objeto JSON. Los arreglos y tipos compuestos se convierten de forma recursiva. Los valores escalares usan una función de conversión si está disponible; de lo contrario, se representan como texto JSON. |
|
|
|
|
Devuelve un arreglo como un arreglo JSON. Los arreglos multidimensionales generan arreglos JSON anidados. Cuando |
|
|
|
|
Construye un arreglo JSON a partir de una lista variable de argumentos. Los argumentos pueden ser de distintos tipos. |
|
|
|
|
Construye un objeto JSON a partir de una lista variable de argumentos alternos de clave y valor. |
|
|
|
|
Construye un objeto JSON a partir de un arreglo de texto. Acepta un arreglo unidimensional con un número par de miembros (pares clave-valor alternos) o un arreglo bidimensional donde cada arreglo interno es un par clave-valor. |
|
|
|
|
Construye un objeto JSON a partir de dos arreglos independientes de claves y valores. |
|
|
Indexación de campos JSONB
Hologres V1.1 y versiones posteriores admiten índices GIN y B-tree en campos JSONB para acelerar las consultas. Indexe campos JSONB en lugar de campos JSON, ya que los índices GIN no están disponibles para JSON.
Los índices GIN para JSONB son adecuados para campos JSONB dispersos. Para campos no dispersos, utilice la optimización de almacenamiento orientado a columnas de JSONB. A partir de Hologres V3.0.42 y V3.1.10, los índices GIN y el almacenamiento orientado a columnas no se pueden usar juntos; si habilita ambas opciones, los índices GIN no surten efecto.
Hay dos clases de operadores de índices GIN disponibles:
jsonb_ops(predeterminado): crea una entrada de índice para cada clave y valor. Admite los operadores?,?|,?&y@>.jsonb_path_ops: crea una entrada de índice solo para cada valor. Admite únicamente el operador@>.
-- Create a GIN index using jsonb_ops (default)
CREATE INDEX idx_name ON table_name USING gin (idx_col);
-- Create a GIN index using jsonb_path_ops
CREATE INDEX idx_name ON table_name USING gin (idx_col jsonb_path_ops);
Operadores nativos de PostgreSQL
Los índices GIN nativos de PostgreSQL requieren una nueva verificación de datos después de la recuperación, lo cual puede limitar la mejora en el rendimiento de las consultas. Los ejemplos siguientes muestran ambas clases de operadores.
Ejemplo: índice jsonb_ops con una consulta de existencia de clave
-- 1. Create a table.
BEGIN;
DROP TABLE IF EXISTS json_table;
CREATE TABLE IF NOT EXISTS json_table
(
id INT,
j jsonb
);
COMMIT;
-- 2. Create a GIN index using jsonb_ops.
CREATE INDEX index_json ON json_table USING GIN(j);
-- 3. Insert data.
INSERT INTO json_table VALUES
(1, '{"key1": 1, "key2": [1, 2], "key3": {"a": "b"}}'),
(1, '{"key1": 1}'),
(2, '{"key2": [1, 2], "key3": {"a": "b"}}');
-- 4. Query rows where key1 exists.
SELECT * FROM json_table WHERE j ? 'key1';
Resultado:
id | j
----+-------------------------------------------------
1 | {"key1": 1, "key2": [1, 2], "key3": {"a": "b"}}
1 | {"key1": 1}
Ejecute EXPLAIN para comprobar que se utiliza el índice:
EXPLAIN SELECT * FROM json_table WHERE j ? 'key1';
QUERY PLAN
Gather (cost=0.00..0.26 rows=1000 width=12)
-> Local Gather (cost=0.00..0.23 rows=1000 width=12)
-> Decode (cost=0.00..0.23 rows=1000 width=12)
-> Bitmap Heap Scan on json_table (cost=0.00..0.13 rows=1000 width=12)
Recheck Cond: (j ? 'key1'::text)
-> Bitmap Index Scan on index_json (cost=0.00..0.00 rows=0 width=0)
Index Cond: (j ? 'key1'::text)
Optimizer: HQO version 1.3.0
El paso Bitmap Index Scan confirma que se utiliza el índice.
Ejemplo: índice jsonb_path_ops con una consulta de contención
-- 1. Create a table.
BEGIN;
DROP TABLE IF EXISTS json_table;
CREATE TABLE IF NOT EXISTS json_table
(
id INT,
j jsonb
);
COMMIT;
-- 2. Create a GIN index using jsonb_path_ops.
CREATE INDEX index_json ON json_table USING GIN(j jsonb_path_ops);
-- 3. Insert 1,000,000 rows.
INSERT INTO json_table (
SELECT
i,
('{
"key1": "'||i||'"
,"key2": "'||i%100||'"
,"key3": "'||i%1000||'"
,"key4": "'||i%10000||'"
,"key5": "'||i%100000||'"
}')::jsonb
FROM generate_series(1, 1000000) i
);
-- 4. Query rows containing '{"key1": "10"}'.
SELECT * FROM json_table WHERE j @> '{"key1": "10"}'::JSONB;
Resultado:
id | j
----+------------------------------------------------------------------------
10 | {"key1": "10", "key2": "10", "key3": "10", "key4": "10", "key5": "10"}
(1 row)
Ejecute EXPLAIN para comprobar que se utiliza el índice:
EXPLAIN SELECT * FROM json_table WHERE j @> '{"key1": "10"}'::JSONB;
QUERY PLAN
-------------------------------------------------------------------------------------------
Gather (cost=0.00..0.26 rows=1000 width=12)
-> Local Gather (cost=0.00..0.23 rows=1000 width=12)
-> Decode (cost=0.00..0.23 rows=1000 width=12)
-> Bitmap Heap Scan on json_table (cost=0.00..0.13 rows=1000 width=12)
Recheck Cond: (j @> '{"key1": "10"}'::jsonb)
-> Bitmap Index Scan on index_json (cost=0.00..0.00 rows=0 width=0)
Index Cond: (j @> '{"key1": "10"}'::jsonb)
Optimizer: HQO version 1.3.0
(8 rows)
Operadores de Hologres
Hologres proporciona dos clases de operadores adicionales, jsonb_holo_ops y jsonb_holo_path_ops, que eliminan el paso de nueva verificación de datos requerido por los índices GIN nativos de PostgreSQL y mejoran el rendimiento de las consultas.
jsonb_holo_opsyjsonb_holo_path_opsadmiten longitudes de índice de 1 a 127 bytes. Los valores de índice superiores a 127 bytes se truncan, y los campos truncados requieren una nueva verificación de datos. EjecuteEXPLAIN ANALYZEpara comprobar si ocurre una nueva verificación.
jsonb_holo_ops: equivalente ajsonb_ops. Admite los operadores?,?|,?&y@>.jsonb_holo_path_ops: equivalente ajsonb_path_ops. Admite únicamente el operador@>.
Ejemplo: índice jsonb_holo_ops
-- 1. Create a table.
BEGIN;
DROP TABLE IF EXISTS json_table;
CREATE TABLE IF NOT EXISTS json_table
(
id INT,
j jsonb
);
COMMIT;
-- 2. Create a GIN index using jsonb_holo_ops.
CREATE INDEX index_json ON json_table USING GIN(j jsonb_holo_ops);
-- 3. Insert data.
INSERT INTO json_table VALUES
(1, '{"key1": 1, "key2": [1, 2], "key3": {"a": "b"}}'),
(1, '{"key1": 1}'),
(2, '{"key2": [1, 2], "key3": {"a": "b"}}');
-- 4. Query rows where key1 exists.
SELECT * FROM json_table WHERE j ? 'key1';
Resultado:
id | j
----+-------------------------------------------------
1 | {"key1": 1}
1 | {"key1": 1, "key2": [1, 2], "key3": {"a": "b"}}
(2 rows)
Ejemplo: índice jsonb_holo_path_ops
-- 1. Create a table.
BEGIN;
DROP TABLE IF EXISTS json_table;
CREATE TABLE IF NOT EXISTS json_table
(
id INT,
j jsonb
);
-- 2. Create a GIN index using jsonb_holo_path_ops.
CREATE INDEX index_json ON json_table USING GIN(j jsonb_holo_path_ops);
-- 3. Insert 1,000,000 rows.
INSERT INTO json_table (
SELECT
i,
('{
"key1": "'||i||'"
,"key2": "'||i%100||'"
,"key3": "'||i%1000||'"
,"key4": "'||i%10000||'"
,"key5": "'||i%100000||'"
}')::jsonb
FROM generate_series(1, 1000000) i
);
-- 4. Query rows containing '{"key1": "10"}'.
SELECT * FROM json_table WHERE j @> '{"key1": "10"}'::JSONB;
Resultado:
id | j
----+------------------------------------------------------------------------
10 | {"key1": "10", "key2": "10", "key3": "10", "key4": "10", "key5": "10"}
(1 row)
Importar datos JSONB desde Realtime Compute for Apache Flink
Cuando importe datos JSON de Realtime Compute for Apache Flink a Hologres, los tipos de campo deben coincidir con los tipos admitidos por cada sistema:
Tablas de origen y de resultados de Flink: defina los campos JSON como
VARCHAR.Tablas internas de Hologres: defina los campos JSON como
JSONB.
Para consultar la asignación completa de tipos de datos, consulte Data type mappings between Realtime Compute for Apache Flink and Hologres.
Paso 1: cree la tabla interna de Hologres con una columna JSONB.
BEGIN;
DROP TABLE IF EXISTS holo_internal_table;
CREATE TABLE IF NOT EXISTS holo_internal_table
(
id BIGINT NOT NULL,
message JSONB NOT NULL
);
CALL set_table_property('holo_internal_table', 'distribution_key', 'id');
COMMIT;
Paso 2: cree las tablas de origen y de resultados de Flink con VARCHAR para el campo JSON y, a continuación, escriba los datos en Hologres.
CREATE TEMPORARY TABLE randomSource (
id BIGINT,
message VARCHAR
)
WITH ('connector' = 'datagen');
CREATE TEMPORARY TABLE sink_holo (
id BIGINT,
message VARCHAR
)
WITH (
'connector' = 'hologres',
'dbname' = '<yourDBname>', -- Hologres database name
'tablename' = '<holo_internal_table>', -- Target Hologres table
'username' = '<yourUsername>', -- Alibaba Cloud AccessKey ID
'password' = '<yourPassword>', -- Alibaba Cloud AccessKey secret
'endpoint' = '<yourEndpoint>' -- VPC endpoint of your Hologres instance
);
INSERT INTO sink_holo
SELECT
1,
'{"k":"v"}'
FROM randomSource;
Almacenamiento orientado a columnas para datos JSONB
Los índices GIN optimizan el rendimiento de las consultas en la capa de cómputo, pero aún requieren examinar todo el contenido JSON durante la ejecución. Hologres V1.3 y versiones posteriores proporcionan almacenamiento orientado a columnas para datos JSONB, lo cual optimiza el proceso en la capa de almacenamiento. Los datos JSONB se almacenan en columnas igual que los datos estructurados, lo que mejora la eficiencia de compresión y acelera las consultas.
Para obtener detalles sobre la instalación y configuración, consulte Accelerate JSONB queries.