Как раскрутить и GROUP BY элементы массива JSON?

8

Учитывая bandтаблицу, со jsonстолбцом, содержащим массив:

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

Как перечислить количество групп, в которые входит каждое название?
Желаемый результат:

name   | count
-------+------------
John   | 2
James  | 2
Thomas | 1
George | 1
Бакс
источник

Ответы:

7

Тип данных столбца peopleявляется json, как результат json_array_elements(people). И нет никакого оператора равенства ( =) для типа данных json. Таким образом, вы также не можете работать GROUP BYна нем. Больше:

jsonbимеет оператор равенства, поэтому «обходной путь» в вашем ответе - привести jsonbи использовать эквивалент jsonb_array_elements(). Состав добавляет стоимость:

jsonb_array_elements(people::jsonb)

Начиная с Postgres 9.4 мы также json_array_elements_text(json)возвращаем элементы массива как text. Связанные с:

Так:

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

Кажется более удобным получать имена, поскольку textвместо jsonbобъектов (двойные кавычки в текстовом представлении) и ваш «желаемый вывод» указывает на то, что вы хотите / нуждаетесь textв результате для начала.

GROUP BYна textданных также дешевле, чем на jsonb, поэтому этот альтернативный «обходной путь» должен быть быстрее по двум причинам. (Тест с EXPLAIN (ANALYZE, TIMING OFF).)

Для протокола, в вашем первоначальном ответе не было ничего плохого . Запятая ( ,) так же «правильна», как и CROSS JOIN LATERAL. Будучи определенным ранее в стандартном SQL, не делает его хуже. Видеть:

Это и не более переносимый на другую СУБД, и так jsonb_array_elements()или json_array_elements_text()не переносимые на другую СУБД , чтобы начать с того , что это тоже не имеет значение. Короткий запрос не проясняется с CROSS JOIN LATERALИМО, но последний момент - только мое личное мнение.

Я использовал более явный псевдоним таблицы и столбца p(name)и ссылку на таблицу p.nameдля защиты от возможных повторяющихся имен. nameЭто такое общее слово, оно может также появиться как имя столбца в базовой таблице band, в этом случае он будет молча разрешить band.name. Простая форма json_array_elements_text(people) nameтолько присоединяет псевдоним таблицы , имя столбца остается value, как возвращено из функции. Но nameразрешается к одному столбцу valueпри использовании в SELECTсписке. Это работает, как ожидалось . Но истинное имя столбца name(если оно band.nameдолжно существовать) будет привязано первым. Хотя это не будет кусаться в данном примере, в других случаях это может быть заряженный ножной пистолет .

Не используйте общее имя в качестве идентификатора для начала. Может быть, это было просто для простого теста.


Если столбец peopleможет содержать что угодно, кроме простого массива JSON , любой запрос вызовет исключение. Если вы не можете гарантировать целостность данных, вы можете защитить 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

Исключает нарушающие строки из запроса.

Связанные с:

Эрвин Брандштеттер
источник
4

Основываясь на комментарии @ ypercubeᵀᴹ, я получил:

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

Просто используется jsonb_array_elementsвместо unnest.

Бакс
источник
-1

Для кого-то в 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

с подтаблицей, как: Colum: idx, строка: 0,1,2,3,4,5,6,7,8,9 ...

Лонг Айви
источник