按 JSONB 数组内的唯一值分组

pym*_*kin 4 postgresql aggregate-functions jsonb

考虑以下表结构:

CREATE TABLE residences (id int, price int, categories jsonb);

INSERT INTO residences VALUES
  (1, 3, '["monkeys", "hamsters", "foxes"]'),
  (2, 5, '["monkeys", "hamsters", "foxes", "foxes"]'),
  (3, 7, '[]'),
  (4, 11, '["turtles"]');

SELECT * FROM residences;

 id | price |                categories
----+-------+-------------------------------------------
  1 |     3 | ["monkeys", "hamsters", "foxes"]
  2 |     5 | ["monkeys", "hamsters", "foxes", "foxes"]
  3 |     7 | []
  4 |    11 | ["turtles"]
Run Code Online (Sandbox Code Playgroud)

现在我想知道每个类别有多少套住宅,以及它们的价格总和。我发现的唯一方法是使用子查询:

SELECT category, SUM(price), COUNT(*) AS residences_no
FROM
  residences a,
  (
    SELECT DISTINCT(jsonb_array_elements(categories)) AS category
    FROM residences
  ) b
WHERE a.categories @> category
GROUP BY category
ORDER BY category;

  category  | sum | residences_no
------------+-----+---------------
 "foxes"    |   8 |             2
 "hamsters" |   8 |             2
 "monkeys"  |   8 |             2
 "turtles"  |  11 |             1
Run Code Online (Sandbox Code Playgroud)

使用不带子查询的jsonb_array_elements将返回狐狸的三个住所,因为第二行中有重复的条目。住宅的价格也将上涨5倍。

有没有什么方法可以在不使用子查询的情况下做到这一点,或者有什么更好的方法来实现这个结果?

编辑

最初我没有提到价格栏。

kli*_*lin 5

select category, count(distinct (id, category))
from residences, jsonb_array_elements(categories) category
group by category
order by category;

  category  | count 
------------+-------
 "foxes"    |     2
 "hamsters" |     2
 "monkeys"  |     2
 "turtles"  |     1
(4 rows)
Run Code Online (Sandbox Code Playgroud)

您必须使用派生表来聚合另一列(所有价格均为 10):

select category, count(*), sum(price) total
from (
    select distinct id, category, price
    from residences, jsonb_array_elements(categories) category
) s
group by category
order by category;

  category  | count | total 
------------+-------+-------
 "foxes"    |     2 |    20
 "hamsters" |     2 |    20
 "monkeys"  |     2 |    20
 "turtles"  |     1 |    10
(4 rows)    
Run Code Online (Sandbox Code Playgroud)