web-dev-qa-db-ja.com

WHERE句を使用して配列のn番目の要素を更新する

PostgreSQL 10データベースに「metadata」という名前のproduct列を持つjsonbテーブルがあります。ドキュメントとPostgresを扱うのは初めてです。 jsonbの値は次のようになります。

 {
 "名前": "l33tシャツ"、
 "価格": "1200"、
 "数量": "60"、
 "options":
 {
 "type": "radio"、
 "title": "color"、
 "opts":
 {"value": "red"、 "price": "-100"、 "qty": "30"}、
 {"value": "blue"、 "price": "+200 "、" qty ":" 10 "}、
 {" value ":" green "、" price ":" +300 "、" qty ":" 20 "} 
 
} 
} 

2つの質問:

1. 「opts」配列の特定の要素を選択するにはどうすればよいですか?

select metadata->'options'->'opts'->(element here) from product
where  metadata->'options'->'opts' @> '[{"value" : "blue"}]'

2. 1つ以上が販売されたときに「数量」を更新する方法(現在の「数量」から差し引く)

ガイド/メモへのさらなるリンクを歓迎します。

3
Arash Moosapour
  1. 「opts」配列の特定の要素を選択するにはどうすればよいですか?

要素番号のインデックスを使用します マニュアルの指示に従って 。パスが長いほど、パスの表記は短くなります。

_SELECT metadata -> 'options' -> 'opts' -> 0 AS elem0
     , metadata #> '{options, opts, 0}'     AS elem0_path
FROM   product;
_

n番目の要素を取得します。これは、質問のタイトルが尋ねるように(JSON配列インデックスは0で始まります)しかし、あなたの例は、実際に_"value":"blue"_の要素が必要であることを示しています。それはささいなことではありません。 LATERAL結合でjsonb_array_elements()を使用してネストされたJSON配列のネストを解除し、必要なものをフィルタリングできます。

_SELECT opt AS elem_blue
--   , metadata #> '{options, opts}' -> (arr.ord::int - 1) AS elem_blue2
FROM   product
     , jsonb_array_elements(metadata #> '{options, opts}') WITH ORDINALITY arr(opt, ord)
WHERE  opt ->> 'value' = 'blue';
_

一致する要素が見つからない場合は何も返しません。

_WITH ORDNALITY_と_elem_blue2_はここでは必要ありませんが、次のステップで使用する手法を示しています。

詳細な説明:

2.それらの1つ以上が販売されたときに「現在の数量から差し引く」「数量」を更新する方法

Postgres 9.5以降、 jsonb_set() があります。 first要素のqtyキーの値を更新するには:

_UPDATE product
SET    metadata =  jsonb_set(metadata, '{options, opts, 0, qty}', '"29"', false)
WHERE  ... -- some filter
_

余談:何らかの理由でqty内の整数が数値(_"30"_)ではなく文字列(_30_)として保存されますか?

「青」要素を更新するには、上記の方法で配列インデックスを決定し、さらにUPDATEマジックを使用して、それを完全に動的にします

_UPDATE product p
SET    metadata = jsonb_set(p.metadata, path, qty, false)
FROM   product p1
     , LATERAL ( -- move computations to subquery
   SELECT ARRAY['options', 'opts', (ord - 1)::text, 'qty'] AS path  -- fix off-by-one
        , to_jsonb((opt ->> 'qty')::int - 1)               AS qty   -- subtract here!
   FROM   jsonb_array_elements(p1.metadata #> '{options, opts}') WITH ORDINALITY arr(opt, ord)
   WHERE  opt ->> 'value' = 'blue'
-- AND    ...  -- more filters
-- FOR UPDATE  -- see below
   ) opt
WHERE  p1.product_id = p.product_id  -- use PK for match
_

最後のクエリは、numberを "qty"(_'9'_)に書き込みますが、コメントとして修正した場合、文字列(_'"9"'_)ではありません。

テーブルproductFROM句にもう一度リストして、LATERAL結合(他の方法では不可能)を許可して、それに自己結合する必要があります。

小さな競合状態に注意してください。同時書き込みの負荷が高い場合、サブクエリ(_FOR UPDATE_)にロック句を追加して、他のトランザクションが内部SELECTと外部UPDATEの間の行を変更しないようにすることができます。 。見る:


しかし、実際にはすべきではありません。ご覧のとおり、jsonb(またはany文書型)は、単一の属性の定期的な更新には適していません。それは面倒で比較的高価です。完全なドキュメントの新しいバージョンを含む新しい行を毎回書き込む必要があります。代わりに、正規化されたDB設計を使用してください。比較的小さな行のみを書き換える必要があり、他の部分やインデックスは変更されません。

数量のみが定期的に更新される場合は、それを1:nテーブルに移動し、VIEWまたは_MATERIALIZED VIEW_のJSONドキュメントにマージします。再設計はこの質問の範囲を超えています。

3