Pregunta
¿Cómo importo arrays de JSON y cómo puedo consultar los objetos internos?
Respuesta
Guarda este array JSON de una sola línea en sample.json
{"_id":"1","channel":"help","events":[{"eventType":"open","time":"2021-06-18T09:42:39.527Z"},{"eventType":"close","time":"2021-06-18T09:48:05.646Z"}]},{"_id":"2","channel":"help","events":[{"eventType":"open","time":"2021-06-18T09:42:39.535Z"},{"eventType":"edit","time":"2021-06-18T09:42:41.317Z"}]},{"_id":"3","channel":"questions","events":[{"eventType":"close","time":"2021-06-18T09:42:39.543Z"},{"eventType":"create","time":"2021-06-18T09:52:51.299Z"}]},{"_id":"4","channel":"general","events":[{"eventType":"create","time":"2021-06-18T09:42:39.552Z"},{"eventType":"edit","time":"2021-06-18T09:47:29.109Z"}]},{"_id":"5","channel":"general","events":[{"eventType":"edit","time":"2021-06-18T09:42:39.560Z"},{"eventType":"open","time":"2021-06-18T09:42:39.680Z"},{"eventType":"close","time":"2021-06-18T09:42:41.207Z"},{"eventType":"edit","time":"2021-06-18T09:42:43.372Z"},{"eventType":"edit","time":"2021-06-18T09:42:45.642Z"}]}Verifica los datos:
clickhousebook.local :) SELECT * FROM file('/path/to/sample.json','JSONEachRow');
SELECT *
FROM file('/path/to/sample.json', 'JSONEachRow')
Query id: 0bbfa09f-ac7f-4a1e-9227-2961b5ffc2d4
┌Cree una tabla para recibir las filas en JSON:
clickhousebook.local :) CREATE TABLE IF NOT EXISTS sample_json_objects_array (
`rawJSON` String EPHEMERAL,
`_id` String DEFAULT JSONExtractString(rawJSON, '_id'),
`channel` String DEFAULT JSONExtractString(rawJSON, 'channel'),
`events` Array(JSON) DEFAULT JSONExtractArrayRaw(rawJSON, 'events')
) ENGINE = MergeTree
ORDER BY
channel
CREATE TABLE IF NOT EXISTS sample_json_objects_array
(
`rawJSON` String EPHEMERAL,
`_id` String DEFAULT JSONExtractString(rawJSON, '_id'),
`channel` String DEFAULT JSONExtractString(rawJSON, 'channel'),
`events` Array(JSON) DEFAULT JSONExtractArrayRaw(rawJSON, 'events')
)
ENGINE = MergeTree
ORDER BY channel
Query id: d02696dd-3f9f-4863-be2a-b2c9a1ae922d
0 rows in set. Elapsed: 0.173 sec. Inserte los datos:
clickhousebook.local :) INSERT INTO
sample_json_objects_array
SELECT
*
FROM
file(
'/opt/cases/000000/sample_json_objects_arrays.json',
'JSONEachRow'
);
INSERT INTO sample_json_objects_array SELECT *
FROM file('/opt/cases/000000/sample.json', 'JSONEachRow')
Query id: 60c4beab-3c2c-40c1-9c6f-bbbd7118dde3
Ok.
0 rows in set. Elapsed: 0.002 sec.Comprueba cómo se aplicó la inferencia de datos al tipo de objeto JSON:
clickhousebook.local :) DESCRIBE TABLE sample_json_objects_array SETTINGS describe_extend_object_types = 1;
DESCRIBE TABLE sample_json_objects_array
SETTINGS describe_extend_object_types = 1
Query id: 302c0c84-1b63-4f60-ad95-d91c0267b0d4
┌Events es un Array de Tuple, cada uno con los campos eventType String y time String. Este último tipo no es el más adecuado (preferiríamos DateTime en su lugar).
Veamos los datos:
clickhousebook.local :) SELECT
_id,
channel,
events.eventType,
events.time
FROM sample_json_objects_array
WHERE has(events.eventType, 'close')
SELECT
_id,
channel,
events.eventType,
events.time
FROM sample_json_objects_array
WHERE has(events.eventType, 'close')
Query id: 3ddd6843-5206-4f52-971f-1699f0ba1728
┌Ejecutemos algunas consultas:
_id y channel de los eventos cuyo eventType tiene el valor close
clickhousebook.local :) SELECT
_id,
channel,
events.eventType
FROM
sample_json_objects_array
WHERE
has(events.eventType,'close')
SELECT
_id,
channel,
events.eventType
FROM sample_json_objects_array
WHERE has(events.eventType, 'close')
Query id: 033a0c56-7bfa-4261-a334-7323bdc40f87
┌Queremos consultar time, por ejemplo, todos los eventos dentro de un intervalo de tiempo determinado, pero vemos que se importó como String:
clickhousebook.local :) SELECT toTypeName(events.time) FROM sample_json_objects_array;
SELECT toTypeName(events.time)
FROM sample_json_objects_array
Query id: 27f07f02-66cd-420d-8623-eeed7d501014
┌Por tanto, para tratarlos como fechas, primero debemos convertirlos a DateTime.
Para convertir un array, usamos la función map:
clickhousebook.local :)
SELECT
_id,
channel,
arrayMap(x->parseDateTimeBestEffort(x), events.time)
FROM
sample_json_objects_array
SELECT
_id,
channel,
arrayMap(x -> parseDateTimeBestEffort(x), events.time)
FROM sample_json_objects_array
Query id: f3c7881e-b41c-4872-9c67-5c25966599a1
┌podemos ver las diferencias al usar toTypeName en ambos arrays:
clickhousebook.local :) SELECT
_id,
channel,
toTypeName(events.time) as events_as_strings,
toTypeName(arrayMap(x->parseDateTimeBestEffort(x), events.time)) as events_as_datetime
FROM
sample_json_objects_array
SELECT
_id,
channel,
toTypeName(events.time) AS events_as_strings,
toTypeName(arrayMap(x -> parseDateTimeBestEffort(x), events.time)) AS events_as_datetime
FROM sample_json_objects_array
Query id: 1af54994-b756-472f-88d7-8b5cdca0e54e
┌ahora obtengamos el id de las filas en las que time está dentro de un intervalo dado.
usamos arrayCount para ver si hay un recuento mayor que 0 de elementos en el array devuelto por la función map que cumplen la condición x BETWEEN toDateTime('2021-06-18 11:46:00', 'Europe/Rome') AND toDateTime('2021-06-18 11:50:00', 'Europe/Rome')
clickhousebook.local :) SELECT
_id,
arrayMap(x -> parseDateTimeBestEffort(x), events.time)
FROM
sample_json_objects_array
WHERE
arrayCount(
x -> x BETWEEN toDateTime('2021-06-18 11:46:00', 'Europe/Rome')
AND toDateTime('2021-06-18 11:50:00', 'Europe/Rome'),
arrayMap(x -> parseDateTimeBestEffort(x), events.time)
) > 0;
SELECT
_id,
arrayMap(x -> parseDateTimeBestEffort(x), events.time)
FROM sample_json_objects_array
WHERE arrayCount(x -> ((x >= toDateTime('2021-06-18 11:46:00', 'Europe/Rome')) AND (x <= toDateTime('2021-06-18 11:50:00', 'Europe/Rome'))), arrayMap(x -> parseDateTimeBestEffort(x), events.time)) > 0
Query id: d4882fc3-9f99-4e87-9f89-47683f10656d
┌⚠️
Recuerde que, en el momento de redactar este artículo, la implementación actual de JSON es experimental y no es apta para producción.
Este ejemplo muestra cómo importar JSON rápidamente y empezar a consultarlo, y refleja el equilibrio entre la facilidad de uso —al importar los objetos JSON como tipo JSON sin necesidad de definir de antemano el esquema—. Esto es práctico para una prueba rápida; sin embargo, para el uso de los datos a largo plazo, en este ejemplo convendría almacenarlos con los tipos más adecuados; por ejemplo, para el campo time, usar DateTime en lugar de String, a fin de evitar cualquier conversión posterior a la ingestión, como se muestra arriba. Consulte la documentación para obtener más información sobre cómo gestionar JSON.