web-dev-qa-db-ja.com

PostgresQLでサブセットJSONを選択する方法

明示的なJSONスキーマまたは規則がないJSON列を処理しようとしています。 JSON内にはさまざまなキーがあります。一部のキーは存在しません。一部のキーは存在しますが、空の文字列値です。

例えば:

CREATE TABLE json_table (
    id         int,
    json_data   json
);

INSERT INTO json_table (id, json_data) VALUES
  (1, '{"foo" : "bar", "baz": "biz"}');
  (2, '{"foo" : "", "baz": "biz"}');
  (3, '{"hello" : "world"}');
  (4, '{"hello" : "world2", "foo" : "bar2", "baz" : "" }');
  1. このテーブルをクエリして、空ではない文字列であるキーと値のペアのみでjson_dataのサブセットを選択/生成することはできますか?

  2. このテーブルをクエリして、指定されたリスト内にあるJSONキーを含むレコードを選択することはできますか? (私は基本的に、キー「foo」を持つjson_dataのレコードを検索したい)

#2の場合、json_dataキーが正規表現パターン内にあるレコードのみを選択するソリューションがあります。

SELECT tmp.* 
FROM (
    SELECT id, 
           ARRAY(SELECT json_object_keys(json_data))::text AS keys
    FROM json_table
) tmp 
WHERE tmp.keys LIKE ANY(ARRAY['%foo%', '%bar%']);

これにより、

"id","keys"
1,"{foo,baz}"
2,"{foo,baz}"
4,"{hello,foo,baz}"

ただし、注意が必要です。キーはあるが空の文字列値を持つレコードを返します!これに#1のソリューションを組み込み、JSONを「有効な」キーと値のペアのみに事前にフィルタリングする方法はありますか?

1

集計中に空の値を削除できます。そして、最後の選択で配列演算子を使用して、特定のキーを含む行のみを取得します。

SELECT tmp.* 
FROM (
  SELECT id, 
         ARRAY(SELECT t.k
               from json_each_text(json_data) as t(k,v)
               where nullif(trim(t.v),'') is not null) AS keys
  FROM json_table
) tmp 
WHERE keys && array['foo','bar']
;

キーだけでなく完全なJSON値が必要な場合は、次のようなものを使用できます。

SELECT tmp.* 
FROM (
  SELECT id, 
         (SELECT jsonb_object_agg(t.k, t.v)
               from json_each_text(json_data) as t(k,v)
               where nullif(trim(t.v),'') is not null) AS keys
  FROM json_table
) tmp 
WHERE keys ?| array['foo','bar']
;

(この場合、処理がはるかに柔軟であるため、この場合の結果にはjsonbを使用しています。一般に、最近ではjsonよりもjsonbを使用することをお勧めします)


Jsonを頻繁にクリーンアップする必要がある場合は、そのための関数を作成することを検討してください。

create function non_empty_values(p_input jsonb)
  returns jsonb
as
$$
  SELECT jsonb_object_agg(t.k, t.v)
  from jsonb_each(jsonb_strip_nulls(p_input)) as t(k,v)
  where trim(t.v::text) <> '';
$$
language sql
immutable;