¿Cómo deshacer y agrupar elementos de una matriz JSON?


8

Dada la bandtabla, con una jsoncolumna que contiene una matriz:

id | people
---+-------------
1  | ['John', 'Thomas']
2  | ['John', 'James']
3  | ['James', 'George']

¿Cómo enumerar el número de bandas de las que forma parte cada nombre?
Salida deseada:

name   | count
-------+------------
John   | 2
James  | 2
Thomas | 1
George | 1

Respuestas:


7

El tipo de datos de la columna peoplees json, como es el resultado de json_array_elements(people). Y no hay operador de igualdad ( =) para el tipo de datos json. Entonces tampoco puedes ejecutarlo GROUP BY. Más:

jsonbtiene un operador de igualdad, por lo que la "solución" en su respuesta es emitir jsonby usar el equivalente jsonb_array_elements(). El elenco agrega costo:

jsonb_array_elements(people::jsonb)

Desde Postgres 9.4 también tenemos json_array_elements_text(json)elementos de matriz de retorno como text. Relacionado:

Entonces:

SELECT p.name, count(*) AS c
FROM   band b, json_array_elements_text(b.people) p(name)
GROUP  BY p.name;

Parece más conveniente obtener nombres en textlugar de jsonbobjetos (entre comillas dobles en la representación de texto) y su "resultado deseado" indica que quiere / necesita texten el resultado para empezar.

GROUP BYon textdata también es más barato que on jsonb, por lo que esta "solución" alternativa debería ser más rápida por dos razones. (Prueba con EXPLAIN (ANALYZE, TIMING OFF).)

Para el registro, no hubo nada malo con su respuesta original . La coma ( ,) es tan "correcta" como CROSS JOIN LATERAL. Haber sido definido anteriormente en SQL estándar no lo hace inferior. Ver:

Tampoco es más portátil para otros RDBMS, y dado que jsonb_array_elements()o json_array_elements_text()no son portátiles para otros RDBMS, eso también es irrelevante. La breve consulta no se aclara con CROSS JOIN LATERALIMO, pero el último bit es solo mi opinión personal.

Utilicé el alias de tabla y columna más explícito p(name)y la referencia calificada de tabla p.namepara defenderme de posibles nombres duplicados. namees una palabra tan común, también puede aparecer como nombre de columna en la tabla subyacente band, en cuyo caso se resolvería en silencio band.name. La forma simple json_array_elements_text(people) namesolo adjunta un alias de tabla , el nombre de la columna sigue siendo value, tal como lo devuelve la función. Pero nameresuelve su columna única valuecuando se usa en la SELECTlista. Se pasa a trabajar como se esperaba . Pero un verdadero nombre de columna name(si band.namedebería existir) se uniría primero. Si bien eso no morderá en el ejemplo dado, puede ser una pistola de pie cargada en otros casos.

Para empezar, no use el "nombre" genérico como identificador. Tal vez eso fue solo por el simple caso de prueba.


Si la columna peoplepuede contener cualquier cosa menos una matriz JSON simple , cualquiera de las consultas desencadenará una excepción. Si no puede garantizar la integridad de los datos, puede defender con json_typeof():

SELECT p.name, count(*) AS c
FROM   band b, json_array_elements_text(b.people) p(name)
WHERE  json_typeof(b.people) = 'array'
GROUP  BY 1; -- optional short syntax since you seem to prefer short syntax

Excluye las filas infractoras de la consulta.

Relacionado:


4

Basado en el comentario de @ ypercubeᵀᴹ terminé con:

SELECT name, count(*) as c
FROM band 
CROSS JOIN LATERAL jsonb_array_elements(people::jsonb) as name
GROUP BY name;

Solo se usa en jsonb_array_elementslugar de unnest.


-1

Para alguien en MySQL

SELECT
  JSON_EXTRACT(people, CONCAT('$[', idx, ']')) AS name, count(*) as count
FROM yourtable
JOIN subtable AS indexes
WHERE JSON_EXTRACT(people, CONCAT('$[', idx, '].id')) IS NOT NULL
group by name

con subtabla como: Colum: idx, fila: 0,1,2,3,4,5,6,7,8,9 ...

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.