web-dev-qa-db-ja.com

JSON配列の要素をネスト解除してGROUP BYする方法は?

bandテーブルがあり、配列を保持するjson列があるとします。

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

それぞれの名前が含まれているバンドの数をリストする方法は?
必要な出力:

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

peopleのデータ型は、json_array_elements(people)の結果と同様に、jsonです。また、データ型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が必要/必要であることを示します。

textデータの_GROUP BY_は、jsonbのデータよりも安価であるため、この代替の「回避策」は2つの理由で高速になるはずです。 (EXPLAIN (ANALYZE, TIMING OFF)でテストします。)

記録として、 元の答え には何も問題はありませんでした。コンマ(_,_)は_CROSS JOIN LATERAL_と同じように「正しい」。標準SQLで以前に定義されていても、それが劣ることはありません。見る:

また、他のRDBMSへの移植性も高くありません。また、jsonb_array_elements()またはjson_array_elements_text()は、そもそも他のRDBMSへの移植性がないため、無関係です。短いクエリは_CROSS JOIN LATERAL_ IMOでは明確になりませんが、最後のビットは私の個人的な意見です。

より明示的なテーブルと列のエイリアスp(name)とテーブル修飾参照_p.name_を使用して、重複する可能性のある名前を防ぎました。 nameは一般的なWordであり、基になるテーブルbandの列名としてポップアップ表示される場合があります。その場合、暗黙的に_band.name_に解決されます。単純なフォームjson_array_elements_text(people) nameは、tableエイリアスのみを付加します。列名は、関数から返された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_

違反している行をクエリから除外します。

関連:

6

@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

4
Bax