web-dev-qa-db-ja.com

JSON配列をPostgres配列に変換する方法は?

次のようなJSONドキュメントを保持するdataタイプの列jsonがあります。

_{
    "name": "foo",
    "tags": ["foo", "bar"]
}
_

ネストされたtags配列を連結文字列(_'foo, bar'_)に変換したいと思います。これは、理論的にはarray_to_string()関数で簡単に可能です。ただし、この関数はjson入力を受け入れません。それで、このJSON配列をPostgres配列(タイプ_text[]_)に変換する方法を知りたいですか?

77
Christoph

Postgres 9.4以降

明らかに この投稿に触発された 、Postgres 9.4は不足している関数を追加しました:
パッチを提供してくれたLaurence RoweとコミットしてくれたAndrew Dunstanに感謝!

JSON配列のネストを解除します。次に array_agg() または ARRAYコンストラクタ を使用して、Postgresarrayを作成します。または string_agg() を作成してtextstringを作成します。

LATERALまたは相関サブクエリの行ごとにネストされていない要素を集計します。次に、元の順序が保持されます。外部クエリにORDER BYGROUP BY、または一意のキーさえ必要ありません。見る:

以下のすべてのSQLコードで、jsonbの「json」を「jsonb」に置き換えます。

SELECT t.tbl_id, d.list
FROM   tbl t
CROSS  JOIN LATERAL (
   SELECT string_agg(d.elem::text, ', ') AS list
   FROM   json_array_elements_text(t.data->'tags') AS d(elem)
   ) d;

短い構文:

SELECT t.tbl_id, d.list
FROM   tbl t, LATERAL (
   SELECT string_agg(value::text, ', ') AS list
   FROM   json_array_elements_text(t.data->'tags')  -- col name default: "value"
   ) d;

関連:

相関サブクエリのARRAYコンストラクタ:

SELECT tbl_id, ARRAY(SELECT json_array_elements_text(t.data->'tags')) AS txt_arr
FROM   tbl t;

関連:

微妙なdifferencenull要素は、実際の配列に保持されます。これは、text値を含むことができないnull文字列を生成する上記のクエリでは不可能です。 true表現は配列です。

関数ラッパー

繰り返し使用する場合、これをさらに簡単にするために、ロジックを関数にカプセル化します。

CREATE OR REPLACE FUNCTION json_arr2text_arr(_js json)
  RETURNS text[] LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
'SELECT ARRAY(SELECT json_array_elements_text(_js))';

SQL関数にして、 inlined より大きくできるようにしますクエリ。
作成するIMMUTABLE(そうであるため)より大きなクエリでの繰り返し評価を回避し、インデックス式で許可します。
並列処理の邪魔にならないようにPARALLEL SAFEPostgres 9.6以降!)にします。見る:

コール:

SELECT tbl_id, json_arr2text_arr(data->'tags')
FROM   tbl;

db <> fiddle ここ


Postgres 9.3以前

関数 json_array_elements() を使用します。しかし、そこから二重引用符付き文字列を取得します。

外部クエリに集計がある代替クエリ。 CROSS JOINは、配列が欠落しているか空の行を削除します。要素の処理にも役立ちます。集計するには一意のキーが必要です。

SELECT t.tbl_id, string_agg(d.elem::text, ', ') AS list
FROM   tbl t
CROSS  JOIN LATERAL json_array_elements(t.data->'tags') AS d(elem)
GROUP  BY t.tbl_id;

引用符で囲まれた文字列を含むARRAYコンストラクタ:

SELECT tbl_id, ARRAY(SELECT json_array_elements(t.data->'tags')) AS quoted_txt_arr
FROM   tbl t;

上記とは異なり、nullはテキスト値「null」に変換されることに注意してください。正しくなく、厳密に言えば、あいまいになる可能性があります。

貧乏人のtrim()による引用解除:

SELECT t.tbl_id, string_agg(trim(d.elem::text, '"'), ', ') AS list
FROM   tbl t, json_array_elements(t.data->'tags') d(elem)
GROUP  BY 1;

Tblから単一の行を取得します。

SELECT string_agg(trim(d.elem::text, '"'), ', ') AS list
FROM   tbl t, json_array_elements(t.data->'tags') d(elem)
WHERE  t.tbl_id = 1;

文字列は相関サブクエリを形成します:

SELECT tbl_id, (SELECT string_agg(trim(value::text, '"'), ', ')
                FROM   json_array_elements(t.data->'tags')) AS list
FROM   tbl t;

ARRAYコンストラクタ:

SELECT tbl_id, ARRAY(SELECT trim(value::text, '"')
                     FROM   json_array_elements(t.data->'tags')) AS txt_arr
FROM   tbl t;

オリジナル(古い) SQL Fiddle
db <> fiddle こちら。

関連:

ノート(9.4ページ以降廃止)

json_array_elements_text(json)json_array_elements(json)の双子が必要になりますJSON配列から適切なtext値を返します。しかし、それは JSON関数の提供された武器 から欠落しているようです。または、スカラーtext値からJSON値を抽出する他の関数。私も欠けているようです。
だから私はtrim()で即興でしたが、それは重要なケースでは失敗します...

99

PG 9.4 +

受け入れられた答えは間違いなくあなたが必要とするものですが、簡単にするために、ここで私がこれに使用するヘルパーがあります:

CREATE OR REPLACE FUNCTION jsonb_array_to_text_array(p_input jsonb)
 RETURNS text[]
 LANGUAGE sql
 IMMUTABLE
AS $function$

SELECT array_agg(ary)::text[] FROM jsonb_array_elements_text(p_input) AS ary;

$function$;

次に、次のようにします。

SELECT jsonb_array_to_text_array('["a", "b", "c"]'::jsonb);

コメントに応じて2020年2月23日更新:コメントの方が効率的かもしれません。投稿した時点では、モジュール化されたソリューションは提供されていなかったため、最適ではないにせよ、真剣に提供しました。それ以来、Erwinは答えをシンプルで効率的な関数に更新しているので、私は更新していません。この回答にはまだ注目が集まっているので、今すぐ更新してください

これは私を噛んだだけなので、もう1つの更新:上記の関数は、値がない場合にnullを返します。これは、状況によっては望ましくない場合があります。次の関数は、値がnullでない場合は空の配列を返しますが、入力がnullの場合もnullを返します。

CREATE OR REPLACE FUNCTION jsonb_array_to_text_array_strict(p_input jsonb)
 RETURNS text[]
 LANGUAGE sql
 IMMUTABLE
AS $function$

SELECT 
  CASE 
    WHEN p_input IS null 
    THEN null 
    ELSE coalesce(ary_out, ARRAY[]::text[]) 
  END
FROM (
  SELECT array_agg(ary)::text[] AS ary_out
  FROM jsonb_array_elements_text(p_input) AS ary
) AS extracted;

$function$
;
17

この質問は PostgreSQLメーリングリスト で行われ、JSONフィールド抽出演算子を使用してJSONテキストをPostgreSQLテキストタイプに変換するハックな方法を思いつきました。

CREATE FUNCTION json_text(json) RETURNS text IMMUTABLE LANGUAGE sql
AS $$ SELECT ('['||$1||']')::json->>0 $$;

db=# select json_text(json_array_elements('["hello",1.3,"\u2603"]'));
 json_text 
-----------
 hello
 1.3
 ☃

基本的には、値を単一要素の配列に変換してから、最初の要素を要求します。

別のアプローチは、この演算子を使用してすべてのフィールドを1つずつ抽出することです。ただし、大規模な配列の場合、配列要素ごとにJSON文字列全体を解析する必要があるため、これは遅くなる可能性が高く、O(n ^ 2)が複雑になります。

CREATE FUNCTION json_array_elements_text(json) RETURNS SETOF text IMMUTABLE LANGUAGE sql
AS $$ SELECT $1->>i FROM generate_series(0, json_array_length($1)-1) AS i $$;

db=# select json_array_elements_text('["hello",1.3,"\u2603"]');
 json_array_elements_text 
--------------------------
 hello
 1.3
 ☃
8
intgr

いくつかのオプションをテストしました。これが私のお気に入りのクエリです。 idとjsonフィールドを含むテーブルがあるとします。 jsonフィールドには、pg配列に変換する配列が含まれています。

SELECT * 
FROM   test 
WHERE  TRANSLATE(jsonb::jsonb::text, '[]','{}')::INT[] 
       && ARRAY[1,2,3];

他の場所よりどこでも速く動作しますが、不自然に見えます

最初にjson配列がテキストとしてキャストされ、次に角括弧が括弧に変更されます。最後に、テキストは必要なタイプの配列としてキャストされます。

SELECT TRANSLATE('[1]'::jsonb::text, '[]','{}')::INT[];

そしてあなたがtext []配列を好むなら

SELECT TRANSLATE('[1]'::jsonb::text, '[]','{}')::TEXT[];
2
FiscalCliff

この質問 の回答から取ったこれらのいくつかの関数は、私が使用しているものであり、うまく機能しています

CREATE OR REPLACE FUNCTION json_array_casttext(json) RETURNS text[] AS $f$
    SELECT array_agg(x) || ARRAY[]::text[] FROM json_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION jsonb_array_casttext(jsonb) RETURNS text[] AS $f$
    SELECT array_agg(x) || ARRAY[]::text[] FROM jsonb_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION json_array_castint(json) RETURNS int[] AS $f$
    SELECT array_agg(x)::int[] || ARRAY[]::int[] FROM json_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION jsonb_array_castint(jsonb) RETURNS int[] AS $f$
    SELECT array_agg(x)::int[] || ARRAY[]::int[] FROM jsonb_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

それらのそれぞれで、空の配列と連結することによって、私は少し頭を悩ませたケースを処理します。その場合、json/jsonbから空の配列をキャストしようとするとこれがないと、期待どおりに空の配列({})の代わりに何も返されません。それらにはいくつかの最適化があると確信していますが、概念を説明するのを簡単にするために、それらはそのまま残されています。

0
Joel B