『PostgreSQL 開発ガイド』第 31 章 JSON ドキュメントの保存

JSON の概要

JSON (JavaScript Object Notation、JavaScript Object Notation) は、プログラミング言語から完全に独立したテキスト形式でデータを保存および表現する軽量のデータ交換形式です。JSON は読み書きが簡単であるだけでなく、機械による解析と生成にも便利であり、ネットワーク伝送効率を効果的に向上させることができます。JSON は、ネットワーク データ転送の分野で XML に代わる強力な代替手段として登場しました。

JSON を学び理解したい場合は、JSON の公式 Web サイトをお勧めします。

JSONデータ型

PostgreSQL は、JSON と JSONB という 2 つの JSON データ型を提供します。これら 2 つのタイプの主な違いはデータの保存形式で、JSONB は処理が容易なバイナリ形式を使用してデータを保存します。

PostgreSQL では、JSONB データ型を推奨します。

次の表では、2 つのデータ型の違いを説明します。

関数 JSON JSONB
保存形式 文字列の元のストレージ 解析されたバイナリ
全文インデックス サポートしません サポート
空白を保持する 予約 保持されない
キーの順序を維持する 予約 保持されない
重複したキーを保持する 予約 保持されない

ストレージ形式が異なるため、JSONB は入力が若干遅くなります (変換が必要) が、クエリははるかに高速です。

以下では主に JSONB データ型を使用しますが、ほとんどの関数は JSON データ型も使用できます。

JSONフィールドを定義する

まず製品テーブル product を作成します。

CREATE TABLE product (
  id INTEGER NOT NULL PRIMARY KEY,
  product_name VARCHAR(100),
  attributes JSONB
);

製品テーブル product には、製品属性を格納するために使用される JSONB タイプのフィールド属性が含まれています。

JSONフィールドの割り当て

文字列を直接使用して JSON フィールドに値を割り当てることができますが、データは有効な JSON 形式である必要があり、そうでない場合はエラーが返されます。

次のステートメントを実行して製品レコードを挿入します。

INSERT INTO product (id, product_name, attributes)
VALUES (1, '椅子','{"color":"棕色", "material":"实木", "height":"60cm"}');

次に、JSON 形式に準拠していないデータを挿入します。

INSERT INTO product (id, product_name, attributes)
VALUES (2, '沙发椅', '"color":"白色:50cm}');

SQL 错误 [22P02]: ERROR: invalid input syntax for type json
  详细:Expected end of input, but found ":".
  位置:71
  在位置:JSON data, line 1: "color":...


Error position: line: 2 pos: 70

次のステートメントは、JSON 配列を含む製品情報を挿入します。

INSERT INTO product (id, product_name, attributes)
VALUES (
  2, 
  '桌子',
  '{"color":"黑色", "material":"金属", "drawers":[{"side":"左侧", "height":"30cm"}, {"side":"右侧", "height":"40cm"}]}'
);

同じ方法を使用して別のレコードを作成します。

INSERT INTO product (id, product_name, attributes)
VALUES (
  3, 
  '茶几',
  '{"color":"棕色", "material":["金属", "实木"]}'
);

上記の方法は簡単ですが、入力が面倒です。この目的のために、PostgreSQL は JSON データを生成するための便利な関数をいくつか提供します。

jsonb_build_object関数は、一連の入力からバイナリ JSON オブジェクトを作成できます。次に例を示します。

SELECT jsonb_build_object('color', '黑色', 'material', '塑料');

jsonb_build_object               |
---------------------------------+
{
   
   "color": "黑色", "material": "塑料"}|

この関数を使用すると、角括弧、カンマ、コロンなどの JSON 記号を手動で入力せずにデータを挿入できます。例えば:

INSERT INTO product (id, product_name, attributes)
VALUES (4, '小型桌子', JSONB_BUILD_OBJECT('color', '黑色', 'material', '塑料'));

JSON データを構築するために一般的に使用されるその他の関数は次のとおりです。

  • json_build_object
  • to_json と to_jsonb
  • array_to_json
  • row_to_json
  • json_build_array と jsonb_build_array
  • json_object と jsonb_object

JSONフィールドデータのクエリ

JSON フィールドのクエリは、通常のフィールドのクエリと何ら変わりません。次に例を示します。

SELECT id, product_name, attributes
FROM product;

id|product_name|attributes                                                                                                        |
--+------------+------------------------------------------------------------------------------------------------------------------+
 1|椅子          |{
   
   "color": "棕色", "height": "60cm", "material": "实木"}                                                               |
 2|桌子          |{
   
   "color": "黑色", "drawers": [{
   
   "side": "左侧", "height": "30cm"}, {
   
   "side": "右侧", "height": "40cm"}], "material": "金属"}|
 3|茶几          |{
   
   "color": "棕色", "material": ["金属", "实木"]}                                                                         |
 4|小型桌子      |{
   
   "color": "黑色", "material": "塑料"}                                                                                 |

単一の属性を取得する

JSON フィールド全体をクエリできるだけでなく、JSON データ内の指定されたノードの属性値を抽出することもできます。例えば:

SELECT id, product_name, attributes -> 'color' AS color
FROM product;

id|product_name|color|
--+------------+-----+
 1|椅子          |"棕色" |
 2|桌子          |"黑色" |
 3|茶几          |"棕色" |
 4|小型桌子      |"黑色" |

演算子 -> ノードのキーを指定することで、該当するデータを取得できます。このメソッドによって返されるデータは、依然として JSON タイプであり、二重引用符で囲まれています。

ノード内のデータ値を文字列として返したい場合は、演算子 ->> を使用できます。例えば:

SELECT id, product_name, attributes -> 'color' AS color
FROM product;

id|product_name|color|
--+------------+-----+
 1|椅子          |棕色   |
 2|桌子          |黑色   |
 3|茶几          |棕色   |
 4|小型桌子      |黑色   |

クエリされた JSON ノードが存在しない場合は、空の値が返されます。

SELECT id, product_name, attributes ->> 'height' AS height
FROM product;

id|product_name|height|
--+------------+------+
 1|椅子          |60cm  |
 2|桌子          |      |
 3|茶几          |      |
 4|小型桌子      |      |

配列のプロパティを取得する

属性ドロワーは JSON 配列であり、その内容をクエリすることもできます。

SELECT id, product_name, attributes ->> 'drawers' AS drawers
FROM product
WHERE id = 2;

id|product_name|drawers                                                             |
--+------------+--------------------------------------------------------------------+
 2|桌子        |[{
   
   "side": "左侧", "height": "30cm"}, {
   
   "side": "右侧", "height": "40cm"}]|

属性ドロワーには 2 つの要素が含まれており、各要素はドロワーを表し、各ドロワーには 2 つの属性があります。

属性ドロワーの最初の要素を表示したい場合は、 -> 演算子を複数回使用できます。

SELECT id, product_name, attributes -> 'drawers' -> 0 AS drawer1
FROM product
WHERE id = 2;

id|product_name|drawer1                         |
--+------------+--------------------------------+
 2|桌子        |{
   
   "side": "左侧", "height": "30cm"}|

最初の -> 演算子は、drawers プロパティを返し、2 番目の -> 演算子は、そのプロパティの最初の配列要素を返します (配列の添え字は 0 から始まります)。

他の 2 つの演算子を使用して、ネストされたプロパティを取得することもできます。次に例を示します。

SELECT id, product_name, 
       attributes #> '{drawers, 1}' AS drawer1, 
       attributes #>> '{drawers, 1}' AS drawer1_text
FROM product
WHERE id = 2;

id|product_name|drawer1                         |drawer1_text                    |
--+------------+--------------------------------+--------------------------------+
 2|桌子        |{
   
   "side": "右侧", "height": "40cm"}|{
   
   "side": "右侧", "height": "40cm"}|

演算子 #> と #>> は、JSON ノードのパスを指定することでネストされたプロパティを取得できます。パスにはキーの名前または配列要素の添え字を含めることができ、戻り値の型はそれぞれ JSON と文字列です。

JSONデータに基づくフィルタリング

色が茶色、材質が無垢材、高さが 60cm の椅子を表示したい場合は、次のクエリを試してみます。

SELECT id, product_name, attributes
FROM product
WHERE attributes = '{"color":"棕色", "material":"实木", "height":"60cm"}';

id|product_name|attributes                                         |
--+------------+---------------------------------------------------+
 1|椅子        |{
   
   "color": "棕色", "height": "60cm", "material": "实木"}|

クエリ条件の文字列が属性フィールドと正確に一致し、完全な属性情報が提供されるため、クエリは期待した結果を返します。

特定の属性 (色など) に基づいて製品のみを検索したい場合、このメソッドは正しいデータを返しません。例えば:

SELECT id, product_name, attributes
FROM product
WHERE attributes = '{"color":"棕色"}';

id|product_name|attributes|
--+------------+----------+

この場合、属性をクエリする前述の方法を使用できます。たとえば、次のようになります。

SELECT id, product_name, attributes
FROM product
WHERE attributes ->> 'color' = '棕色';

id|product_name|attributes                                         |
--+------------+---------------------------------------------------+
 1|椅子        |{
   
   "color": "棕色", "height": "60cm", "material": "实木"}|
 3|茶几        |{
   
   "color": "棕色", "material": ["金属", "实木"]}          |

-> 演算子の代わりに ->> 演算子を使用しました。これは、前者は文字列型を返し、後者は JSON データ型を返すためです。

JSONからデータ行へ

PostgreSQL は、JSON フィールドのデータ行形式への変換をサポートしています。たとえば、 jsonb_each 関数は、各キーと値のペアをレコードに変換できます。

SELECT id, product_name, jsonb_each(attributes)
FROM product;

id|product_name|jsonb_each                                                                                      |
--+------------+------------------------------------------------------------------------------------------------+
 1|椅子          |(color,"""棕色""")                                                                                |
 1|椅子          |(height,"""60cm""")                                                                             |
 1|椅子          |(material,"""实木""")                                                                             |
 2|桌子          |(color,"""黑色""")                                                                                |
 2|桌子          |(drawers,"[{""side"": ""左侧"", ""height"": ""30cm""}, {""side"": ""右侧"", ""height"": ""40cm""}]")|
 2|桌子          |(material,"""金属""")                                                                             |
 3|茶几          |(color,"""棕色""")                                                                                |
 3|茶几          |(material,"[""金属"", ""实木""]")                                                                   |
 4|小型桌子      |(color,"""黑色""")                                                                                |
 4|小型桌子      |(material,"""塑料""")                                                                             |

これに似た関数があります。

  • jsonb_each_text
  • json_each と json_each_text
  • json_array_elements_text からの json_array_elements
  • jsonb_array_elements および jsonb_array_elements_text

json_object_keys 関数または jsonb_object_keys 関数を使用して、JSON フィールド内のすべてのキーの名前を取得することもできます。

SELECT id, product_name, jsonb_object_keys(attributes)
FROM product;

id|product_name|jsonb_object_keys|
--+------------+-----------------+
 1|椅子          |color            |
 1|椅子          |height           |
 1|椅子          |material         |
 2|桌子          |color            |
 2|桌子          |drawers          |
 2|桌子          |material         |
 3|茶几          |color            |
 3|茶几          |material         |
 4|小型桌子      |color            |
 4|小型桌子      |material         |

属性が存在するかどうかを確認する

PostgreSQL には、? 演算子など、JSON 属性の存在を判断するための演算子もいくつか用意されています。

次のステートメントは、drawers 属性を持つ製品を検索します。

SELECT id, product_name, attributes
FROM product
WHERE attributes ? 'drawers' = true;

id|product_name|attributes                                                                                                        |
--+------------+------------------------------------------------------------------------------------------------------------------+
 2|桌子        |{
   
   "color": "黑色", "drawers": [{
   
   "side": "左侧", "height": "30cm"}, {
   
   "side": "右侧", "height": "40cm"}], "material": "金属"}|

他の存在演算子については、公式ドキュメントを参照してください。

JSONフィールドデータを更新する

UPDATE ステートメントを使用して JSON フィールドを更新する場合、 || 演算子を使用して、既存の JSON データに新しいキー値を追加できます。例えば:

UPDATE product
SET attributes = attributes || '{"width":"100cm"}'
WHERE id = 1;

SELECT *
FROM product
WHERE id = 1;

id|product_name|attributes                                                           |
--+------------+---------------------------------------------------------------------+
 1|椅子        |{
   
   "color": "棕色", "width": "100cm", "height": "60cm", "material": "实木"}|

JSONB データ型ではキーの順序が保持されないため、新しいプロパティ幅がデータの途中に追加されました。

別の方法は、次のように jsonb_insert メソッドを使用することです。

UPDATE product
SET attributes = jsonb_insert(attributes, '{"weight"}', '"1kg"')
WHERE id = 3;

SELECT *
FROM product
WHERE id = 3;

id|product_name|attributes                                                |
--+------------+----------------------------------------------------------+
 3|茶几        |{
   
   "color": "棕色", "weight": "1kg", "material": ["金属", "实木"]}|

既存のキーの値を更新する場合は、jsonb_set 関数を使用できます。例えば:

UPDATE product
SET attributes = jsonb_set(attributes, '{height}', '"75cm"')
WHERE id = 1;

SELECT *
FROM product
WHERE id = 1;

id|product_name|attributes                                                           |
--+------------+---------------------------------------------------------------------+
 1|椅子        |{
   
   "color": "棕色", "width": "100cm", "height": "75cm", "material": "实木"}|

上記のステートメントは、ID が 1 に等しい商品の属性の高さを 75cm に変更します。

JSONフィールドデータの削除

JSON フィールド データ全体を削除すると、単純に NULL に設定できます。次に例を示します。

UPDATE product
SET attributes = NULL
WHERE id = 4;

SELECT *
FROM product
WHERE id = 4;

id|product_name|attributes|
--+------------+----------+
 4|小型桌子      |          |

JSON フィールドの属性を削除するには、次のように - 演算子を使用できます。

UPDATE product
SET attributes = attributes - 'height'
WHERE id = 1;

SELECT *
FROM product
WHERE id = 1;

id|product_name|attributes                                         |
--+------------+---------------------------------------------------+
 1|椅子        |{
   
   "color": "棕色", "width": "100cm", "material": "实木"}|

製品 1 の高さ属性は削除されました。

JSON 属性を削除する別の方法は、jsonb_set_lax 関数を利用することです。

UPDATE product
SET attributes = jsonb_set_lax(attributes, '{"width"}', null, false, 'delete_key')
WHERE id = 1;

SELECT *
FROM product
WHERE id = 1;

id|product_name|attributes                       |
--+------------+---------------------------------+
 1|椅子        |{
   
   "color": "棕色", "material": "实木"}|

関数の 3 番目のパラメーターは width 属性を空に設定することを意味し、4 番目のパラメーターは属性が存在しない場合に新しい属性を作成しないことを意味し、5 番目のパラメーターは空に設定された属性を削除することを意味します。

全文インデックス

JSON プロパティに基づいてデータをクエリする場合、パフォーマンスの問題が発生する可能性があります。製品データのバッチを生成します。

WITH RECURSIVE t AS (
  SELECT 10 n, '产品'||10 product_name, '{"color": "棕色", "height": "60cm", "material": "实木"}' attr
  UNION ALL
  SELECT n+1, '产品'||n+1, '{"color": "棕色", "height": "60cm", "material": "实木"}'
  FROM t WHERE t.n<10000
)
INSERT INTO product 
SELECT n, product_name, attr::jsonb FROM t;

次に、次のステートメントの実行計画を見てください。

EXPLAIN
SELECT id, product_name, attributes
FROM product
WHERE attributes @> '{"color":"黑色"}';

QUERY PLAN                                              |
--------------------------------------------------------+
Seq Scan on product  (cost=0.00..258.93 rows=1 width=78)|
  Filter: (attributes @> '{"color": "黑色"}'::jsonb)      |

上記のステートメントは、黒色の製品を見つけることを意味します。実行計画を見ると全表順次スキャンとなっており、データ量が少ない場合は問題ありません。ただし、大量のデータを含むテーブルの場合、クエリ速度が非常に遅くなる可能性があります。

この目的のために、PostgreSQL は JSON フィールドをサポートするフルテキスト インデックスを提供し、クエリのパフォーマンスを最適化できます。このタイプのインデックスは GIN (General Inverted Index) で、通常は検索エンジンで使用されます。

JSON フィールドに基づいてフルテキスト インデックスを作成できます。

CREATE INDEX idx_product_attributes ON product USING GIN(attributes);

キーワード USING GIN は、インデックス タイプを指定するために使用されます。

実行計画を再度確認します。

EXPLAIN
SELECT id, product_name, attributes
FROM product
WHERE attributes @> '{"color":"黑色"}';

QUERY PLAN                                                                          |
------------------------------------------------------------------------------------+
Bitmap Heap Scan on product  (cost=20.00..24.01 rows=1 width=78)                    |
  Recheck Cond: (attributes @> '{"color": "黑色"}'::jsonb)                            |
  ->  Bitmap Index Scan on idx_product_attributes  (cost=0.00..20.00 rows=1 width=0)|
        Index Cond: (attributes @> '{"color": "黑色"}'::jsonb)                        |

この時点で、実行プランはインデックス スキャンを使用し、コストは 258.93 から 24.01 に低下します。

おすすめ

転載: blog.csdn.net/horses/article/details/123263980