JSONとは何ですか?
JSONは「JavaScriptObjectNotation」の略で、Webアプリケーションで一般的に使用されているデータ形式の一種です。これは、データがそのような形式でWebアプリケーションとサーバー間で送信されることを意味します。 XML形式の代わりにJSONが導入されました。 「古き良き時代」では、JSONと比較して重量のあるデータ型であるXML形式で送信されるために使用されたデータ。以下は、JSON形式の文字列の例です。
{ "ID":"001","name": "Ven", "Country": "Australia", "city": "Sydney", "Job Title":"Database Consultant"}
JSON文字列には、以下に示すように、それ自体に別のJSONオブジェクトを含めることができます。
{ "ID":"001", "name": "Ven", "Job Title":"Database Consultant", "Location":{"Suburb":"Dee Why","city": "Sydney","State":"NSW","Country": "Australia"}}
現代のWebおよびモバイルアプリケーションは、ほとんどの場合、「JSONバイト」とも呼ばれるJSON形式でデータを生成します。これは、アプリケーションサーバーによって取得され、データベースに送信されます。 JSONバイトは順番に処理され、個別の列値に分割されてRDBMSテーブルに挿入されます。
例:
{ "ID":"001","name": "Ven", "Country": "Australia", "city": "Sydney", "Job Title":"Database Consultant"}
上記のJSONデータは以下のようなSQLに変換されます。
Insert into test (id, name, country,city,job_title) values (001,'Ven','Australia','Sydney','Database Consultant');
JSONデータの保存と処理に関しては、それをサポートするさまざまなNoSQLデータベースがあり、最も人気のあるものはMongoDBです。 RDBMSデータベースに関しては、最近まで、JSON文字列は通常のテキストとして扱われ、JSON形式の文字列を具体的に認識、保存、または処理するデータ型はありませんでした。最も人気のあるオープンソースのRDBMSデータベースであるPostgreSQLは、JSONデータ型を考案しました。これは、JSONデータの処理に関して、パフォーマンス、機能、およびスケーラビリティに非常に有益であることが判明しました。
PostgreSQL + JSON
JSONデータ型が導入されて以来、PostgreSQLデータベースはますます人気が高まっています。実際、大量のJSONデータの処理に関しては、PostgreSQLはMongoDBを上回っています。アプリケーションは、JSON文字列を標準のJSON形式でPostgreSQLデータベースに保存できます。開発者は、JSON文字列をjsonデータ型としてデータベースに送信し、JSON形式で取得するようにアプリケーションに指示する必要があります。 JSONデータ型にJSON文字列を格納することは、TEXTデータ型に同じものを格納することに比べていくつかの利点があります。 JSONデータ型は、有効なJSON形式の文字列のみを受け入れることができます。文字列が正しいJSON形式でない場合、エラーが生成されます。 JSONデータ型は、アプリケーションが効率的でインデックスベースの検索を実行するのに役立ちます。これについては後ほど詳しく説明します。
JSONデータ型はPostgreSQL-9.2の投稿で導入され、大幅な機能強化が行われました。 PostgreSQL-9.4で主に追加されたのは、JSONBデータ型の追加です。 JSONBは、JSONデータをバイナリ形式で格納するJSONデータ型の高度なバージョンです。これは、PostgreSQLでのJSONデータの検索と処理の方法に大きな違いをもたらした主要な機能拡張です。 JSONデータ型の利点を詳しく見てみましょう。
JSONおよびJSONBデータ型
JSONデータ型は、json形式の文字列を、あまり強力ではなく、検索に使用される多くのJSON関連関数をサポートしないテキストとして格納します。従来のB-TREEインデックスのみをサポートし、JSONデータ全体でより高速で効率的な検索操作に不可欠な他のインデックスタイプをサポートしていません。
JSONデータ型の高度なバージョンであるJSONBは、JSONドキュメントの保存と処理に強くお勧めします。さまざまなjson演算子をサポートし、JSON形式の文字列をバイナリ形式で保存したり、JSON関数とインデックスをサポートして効率的な検索を実行したりするなど、JSONに比べて多くの利点があります。
違いを見てみましょう。
JSON | JSONB | |
---|---|---|
1 | 有効なJSONドキュメントのみを格納するTEXTデータ型によく似ています。 | JSONドキュメントをバイナリ形式で保存します。 |
2 | 空白を含めてJSONドキュメントをそのまま保存します。 | 空白を削除し、より高速で効率的な検索に役立つ形式で保存します |
3 | 全文検索のインデックス作成はサポートしていません | 全文検索のインデックス作成をサポート |
4 | さまざまなJSON関数と演算子をサポートしていません | すべてのJSON関数と演算子をサポートします |
上記の#4の例
JSON
以下はJSONデータ型の表です
dbt3=# \d product
Table "dbt3.product"
Column | Type | Collation | Nullable | Default
----------------+--------+-----------+----------+---------
item_code | bigint | | not null |
productdetails | json | | |
Indexes:
"product_pkey" PRIMARY KEY, btree (item_code)
従来のJSON演算子(「@>」や「#>」など)はサポートしていません。全文検索-JSONデータの検索は、JSONデータ型でサポートされていないSQLで「@>」または「#>」を使用して行われます
dbt3=# select * from product where productdetails @> '{"l_shipmode":"AIR"}' and productdetails @> '{"l_quantity":"27"}';
ERROR: operator does not exist: json @> unknown
LINE 1: select * from product where productdetails @> '{"l_shipmode"...
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
dbt3=#
JSONB
以下は、JSONBデータ型の表です
dbt3=# \d products
Table "dbt3.products"
Column | Type | Collation | Nullable | Default
---------------+--------+-----------+----------+---------
item_code | bigint | | not null |
order_details | jsonb | | |
Indexes:
"products_pkey" PRIMARY KEY, btree (item_code)
演算子(「@>」など)を使用したJSONデータによる全文検索をサポートします
dbt3=# select * from products where order_details @> '{"l_shipmode" : "AIR"}' limit 2;
item_code | order_details
-----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4 | {"l_partkey": 21315, "l_orderkey": 1, "l_quantity": 28, "l_shipdate": "1996-04-21", "l_shipmode": "AIR", "l_commitdate": "1996-03-30", "l_shipinstruct": "NONE", "l_extendedprice": 34616.7}
8 | {"l_partkey": 42970, "l_orderkey": 3, "l_quantity": 45, "l_shipdate": "1994-02-02", "l_shipmode": "AIR", "l_commitdate": "1994-01-04", "l_shipinstruct": "NONE", "l_extendedprice": 86083.6}
(2 rows)
今日のホワイトペーパーをダウンロードするClusterControlを使用したPostgreSQLの管理と自動化PostgreSQLの導入、監視、管理、スケーリングを行うために知っておくべきことについて学ぶホワイトペーパーをダウンロードする JSONデータをクエリする方法
データ操作に関連するいくつかのPostgreSQLJSON機能を見てみましょう。以下は、JSONデータがテーブルでどのように表示されるかを示しています。列「order_details」はJSONBタイプです
dbt3=# select * from product_details ;
item_code | order_details
-----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 | {"l_partkey": 1551894, "l_orderkey": 1, "l_quantity": 17, "l_shipdate": "1996-03-13", "l_shipmode": "TRUCK", "l_commitdate": "1996-02-12", "l_shipinstruct": "DELIVER IN PERSON", "l_extendedprice": 33078.9}
2 | {"l_partkey": 673091, "l_orderkey": 1, "l_quantity": 36, "l_shipdate": "1996-04-12", "l_shipmode": "MAIL", "l_commitdate": "1996-02-28", "l_shipinstruct": "TAKE BACK RETURN", "l_extendedprice": 38306.2}
3 | {"l_partkey": 636998, "l_orderkey": 1, "l_quantity": 8, "l_shipdate": "1996-01-29", "l_shipmode": "REG AIR", "l_commitdate": "1996-03-05", "l_shipinstruct": "TAKE BACK RETURN", "l_extendedprice": 15479.7}
4 | {"l_partkey": 21315, "l_orderkey": 1, "l_quantity": 28, "l_shipdate": "1996-04-21", "l_shipmode": "AIR", "l_commitdate": "1996-03-30", "l_shipinstruct": "NONE", "l_extendedprice": 34616.7}
5 | {"l_partkey": 240267, "l_orderkey": 1, "l_quantity": 24, "l_shipdate": "1996-03-30", "l_shipmode": "FOB", "l_commitdate": "1996-03-14", "l_shipinstruct": "NONE", "l_extendedprice": 28974}
6 | {"l_partkey": 156345, "l_orderkey": 1, "l_quantity": 32, "l_shipdate": "1996-01-30", "l_shipmode": "MAIL", "l_commitdate": "1996-02-07", "l_shipinstruct": "DELIVER IN PERSON", "l_extendedprice": 44842.9}
7 | {"l_partkey": 1061698, "l_orderkey": 2, "l_quantity": 38, "l_shipdate": "1997-01-28", "l_shipmode": "RAIL", "l_commitdate": "1997-01-14", "l_shipinstruct": "TAKE BACK RETURN", "l_extendedprice": 63066.3}
8 | {"l_partkey": 42970, "l_orderkey": 3, "l_quantity": 45, "l_shipdate": "1994-02-02", "l_shipmode": "AIR", "l_commitdate": "1994-01-04", "l_shipinstruct": "NONE", "l_extendedprice": 86083.6}
9 | {"l_partkey": 190355, "l_orderkey": 3, "l_quantity": 49, "l_shipdate": "1993-11-09", "l_shipmode": "RAIL", "l_commitdate": "1993-12-20", "l_shipinstruct": "TAKE BACK RETURN", "l_extendedprice": 70822.1}
10 | {"l_partkey": 1284483, "l_orderkey": 3, "l_quantity": 27, "l_shipdate": "1994-01-16", "l_shipmode": "SHIP", "l_commitdate": "1993-11-22", "l_shipinstruct": "DELIVER IN PERSON", "l_extendedprice": 39620.3}
(10 rows)
発送日を含むすべての商品コードを選択してください
dbt3=# select item_code, order_details->'l_shipdate' as shipment_date from product_details ;
item_code | shipment_date
-----------+---------------
1 | "1996-03-13"
2 | "1996-04-12"
3 | "1996-01-29"
4 | "1996-04-21"
5 | "1996-03-30"
6 | "1996-01-30"
7 | "1997-01-28"
8 | "1994-02-02"
9 | "1993-11-09"
10 | "1994-01-16"
(10 rows)
飛行機で到着したすべての注文のitem_code、数量、価格を取得します
dbt3=# select item_code, order_details->'l_quantity' as quantity, order_details->'l_extendedprice' as price, order_details->'l_shipmode' as price from product_details where order_details->>'l_shipmode'='AIR';
item_code | quantity | price | price
-----------+----------+---------+-------
4 | 28 | 34616.7 | "AIR"
8 | 45 | 86083.6 | "AIR"
(2 rows)
JSON演算子「->」と「->>」は、SQLクエリでの選択と比較に使用されます。 「->」演算子はJSONオブジェクトフィールドを引用符で囲んだフィールドとして返し、演算子「->>」はJSONオブジェクトフィールドをTEXTとして返します。上記の2つのSQLは、JSONフィールド値をそのまま表示する例です。以下は、TEXT形式でJSONフィールドを抽出する例です。
以下は、TEXT形式でJSONフィールドをフェッチする例です。
dbt3=# select item_code, order_details->>'l_shipdate' as shipment_date from product_details ;
item_code | shipment_date
-----------+---------------
1 | 1996-03-13
2 | 1996-04-12
3 | 1996-01-29
4 | 1996-04-21
5 | 1996-03-30
6 | 1996-01-30
7 | 1997-01-28
8 | 1994-02-02
9 | 1993-11-09
10 | 1994-01-16
(10 rows)
「#>」と呼ばれる別の演算子があります。これは、JSON文字列の一部であるJSON要素のデータ部分をクエリするために使用されます。例を見てみましょう。
以下は表のデータです。
dbt3=# select * from test_json ;
id | details
-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
10000 | {"Job": "Database Consultant", "name": "Venkata", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Dee Why", "Country": "Australia"}}
20000 | {"Job": "Database Consultant", "name": "Smith", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Manly", "Country": "Australia"}}
30000 | {"Job": "Developer", "name": "John", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Brookvale", "Country": "Australia"}}
50000 | {"cars": {"Ford": [{"doors": 4, "model": "Taurus"}, {"doors": 4, "model": "Escort"}], "Nissan": [{"doors": 4, "model": "Sentra"}, {"doors": 4, "model": "Maxima"}, {"doors": 2, "model": "Skyline"}]}}
40000 | {"Job": "Architect", "name": "James", "Location": {"city": "Melbourne", "State": "NSW", "Suburb": "Trugnania", "Country": "Australia"}}
「State」「NSW」ですべての詳細を確認したいのですが、「State」はキー「Location」の一部であるJSONオブジェクトキーです。以下は、同じものをクエリする方法です。
dbt3=# select * from test_json where details #> '{Location,State}'='"NSW"';
id | details
-------+------------------------------------------------------------------------------------------------------------------------------------------------
10000 | {"Job": "Database Consultant", "name": "Venkata", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Dee Why", "Country": "Australia"}}
20000 | {"Job": "Database Consultant", "name": "Smith", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Manly", "Country": "Australia"}}
30000 | {"Job": "Developer", "name": "John", "Location": {"city": "Sydney", "State": "NSW", "Suburb": "Brookvale", "Country": "Australia"}}
30000 | {"Job": "Architect", "name": "James", "Location": {"city": "Melbourne", "State": "NSW", "Suburb": "Trugnania", "Country": "Australia"}}
(4 rows)
算術演算はJSONデータに対して実行できます。 JSON列のデータ部分はTEXTであるため、型キャストが必要です。
dbt3=# select item_code, order_details->'l_quantity' as quantity, order_details->'l_extendedprice' as price, order_details->'l_shipmode' as price from product_details where (order_details->'l_quantity')::int > 10;
item_code | quantity | price | price
-----------+----------+---------+---------
1 | 17 | 33078.9 | "TRUCK"
2 | 36 | 38306.2 | "MAIL"
4 | 28 | 34616.7 | "AIR"
5 | 24 | 28974 | "FOB"
6 | 32 | 44842.9 | "MAIL"
7 | 38 | 63066.3 | "RAIL"
8 | 45 | 86083.6 | "AIR"
9 | 49 | 70822.1 | "RAIL"
10 | 27 | 39620.3 | "SHIP"
(9 rows)
上記のすべてとは別に、JOINを含むSQLを使用してJSONで次の操作を実行することもできます
- ORDERBY句を使用したデータの並べ替え
- SUM、AVG、MIN、MAXなどの集計関数を使用した集計
- GROUPBY句を使用してデータをグループ化します
パフォーマンスはどうですか?
JSON列のデータは本質的にテキストであり、データサイズに基づいてパフォーマンスの問題が予想されます。 JSONデータを検索すると、時間と計算能力が低下し、アプリケーションへの応答が遅くなる可能性があります。 DBAは、JSON列にヒットするSQLが十分に高速に応答し、優れたパフォーマンスを発揮することを確認することが不可欠です。データ抽出はSQLを介して行われるため、DBAが探すオプションはインデックス作成の可能性であり、そうです、JSONデータ型はインデックス作成オプションをサポートしています。
JSONがもたらすインデックスオプションを見てみましょう。
JSONBのインデックス作成
JSONBデータ型は、FULL-TEXT-SEARCH索引付けをサポートします。これは、DBAがJSONBデータ型を使用するときに期待するJSONBの最も重要な機能です。 JSONオブジェクトキーの通常のインデックスは、検索クエリでJSON固有の演算子を使用する場合に役立たない場合があります。以下は、全表スキャンに対応するTEXTSEARCHクエリです
dbt3=# explain select * from products where order_details @> '{"l_shipmode" : "AIR"}';
QUERY PLAN
--------------------------------------------------------------------
Seq Scan on products (cost=0.00..4205822.65 rows=59986 width=252)
Filter: (order_details @> '{"l_shipmode": "AIR"}'::jsonb)
(2 rows)
JSONBは、上記のようなクエリに役立つGINと呼ばれる全文検索インデックスタイプをサポートしています。
ここで、GINインデックスを作成して、それが役立つかどうかを確認します
dbt3=# create index od_gin_idx on products using gin(order_details jsonb_path_ops);
CREATE INDEX
以下を確認できる場合、クエリはGINインデックスを取得します
dbt3=# explain select * from products where order_details @> '{"l_shipmode" : "AIR"}';
QUERY PLAN
-------------------------------------------------------------------------------
Bitmap Heap Scan on products (cost=576.89..215803.18 rows=59986 width=252)
Recheck Cond: (order_details @> '{"l_shipmode": "AIR"}'::jsonb)
-> Bitmap Index Scan on od_gin_idx (cost=0.00..561.90 rows=59986 width=0)
Index Cond: (order_details @> '{"l_shipmode": "AIR"}'::jsonb)
そして、GINの代わりにB-TREEインデックスは役に立ちません
dbt3=# create index idx on products((order_details->>'l_shipmode'));
CREATE INDEX
dbt3=# \d products
Table "dbt3.products"
Column | Type | Collation | Nullable | Default
---------------+--------+-----------+----------+---------
item_code | bigint | | not null |
order_details | jsonb | | |
Indexes:
"products_pkey" PRIMARY KEY, btree (item_code)
"idx" btree ((order_details ->> 'l_shipmode'::text))
以下を見ると、クエリは全表スキャンを優先します
dbt3=# explain select * from products where order_details @> '{"l_shipmode" : "AIR"}';
QUERY PLAN
--------------------------------------------------------------------
Seq Scan on products (cost=0.00..4205822.65 rows=59986 width=252)
Filter: (order_details @> '{"l_shipmode": "AIR"}'::jsonb)
GINインデックスとは何ですか?
GINはGeneralizedInvertedIndexの略です。 GIN Indexのコア機能は、全文検索を高速化することです。テキストまたはドキュメント内の特定のキーまたは要素に基づいて検索を実行する場合は、GINインデックスが最適です。 GINインデックスは、「キー」(または要素または値)と「位置リスト」のペアを格納します。位置リストは、キーのrowIDです。つまり、「キー」がドキュメント内の複数の場所で発生する場合、GINインデックスは、キーをその発生位置とともに1回だけ保存します。これにより、GINインデックスのサイズがコンパクトに保たれるだけでなく、検索が大幅に高速化されます。仕方。これはPostgres-9.4の拡張機能です。
GINインデックスの課題
データの複雑さによっては、GINインデックスの維持に費用がかかる場合があります。 GINインデックスの作成は、インデックスがキーとその行IDを見つけるためにドキュメント全体を検索する必要があるため、時間とリソースを消費します。 GINインデックスが肥大化すると、さらに困難になる可能性があります。また、データサイズと複雑さによっては、GINインデックスのサイズが非常に大きくなる可能性があります。
JSONのインデックス作成
JSONは、テキスト検索とGINのようなインデックスをサポートしていません
dbt3=# create index pd_gin_idx on product using gin(productdetails jsonb_path_ops);
ERROR: operator class "jsonb_path_ops" does not accept data type json
B-TREEのような通常のインデックス作成は、JSONとJSONBの両方でサポートされています
はい、B-TREEインデックスのような通常のインデックスは、JSONとJSONBの両方のデータ型でサポートされており、テキスト検索操作には役立ちません。各JSONオブジェクトキーには個別にインデックスを付けることができます。これは、WHERE句で同じオブジェクトキーが使用されている場合にのみ役立ちます。
JSONBでB-TREEインデックスを作成し、その動作を確認しましょう。
dbt3=# create index idx on products((order_details->>'l_shipmode'));
CREATE INDEX
dbt3=# \d products
Table "dbt3.products"
Column | Type | Collation | Nullable | Default
---------------+--------+-----------+----------+---------
item_code | bigint | | not null |
order_details | jsonb | | |
Indexes:
"products_pkey" PRIMARY KEY, btree (item_code)
"idx" btree ((order_details ->> 'l_shipmode'::text))
B-TREEインデックスは、演算子(「@>」など)を使用してJSONデータで全文検索を実行するSQLの高速化には役立たず、このようなインデックスは、次のようなクエリの高速化にのみ役立つことをすでに学びました。以下のものは、典型的なRDBMSタイプのSQLです(検索クエリではありません)。各JSONオブジェクトキーには個別にインデックスを付けることができます。これにより、インデックス付きのJSONオブジェクトキーをWHERE句で使用すると、クエリの速度が向上します。
次のクエリの例では、WHERE句で「l_shipmode」オブジェクトキーを使用しています。インデックスが作成され、クエリはインデックススキャンを実行します。別のオブジェクトキーを使用して検索する場合、クエリは全表スキャンを実行することを選択します。
dbt3=# explain select * from products where order_details->>'l_shipmode'='AIR';
QUERY PLAN
---------------------------------------------------------------------------------
Index Scan using idx on products (cost=0.56..1158369.34 rows=299930 width=252)
Index Cond: ((order_details ->> 'l_shipmode'::text) = 'AIR'::text)
同じことがJSONデータ型でも機能します
dbt3=# create index idx on products((order_details->>'l_shipmode'));
CREATE INDEX
dbt3=# \d products
Table "dbt3.products"
Column | Type | Collation | Nullable | Default
---------------+--------+-----------+----------+---------
item_code | bigint | | not null |
order_details | json | | |
Indexes:
"products_pkey" PRIMARY KEY, btree (item_code)
"idx" btree ((order_details ->> 'l_shipmode'::text))
観察できる場合、クエリはインデックスを使用しています
dbt3=# explain select * from products where order_details->>'l_shipmode'='AIR';
QUERY PLAN
---------------------------------------------------------------------------------
Index Scan using idx on products (cost=0.56..1158369.34 rows=299930 width=252)
Index Cond: ((order_details ->> 'l_shipmode'::text) = 'AIR'::text)
結論
PostgreSQLJSONデータを使用するときに覚えておくべきことがいくつかあります...
- PostgreSQLは、JSONデータを保存および処理するための最良のオプションの1つです
- すべての強力な機能を備えたPostgreSQLをドキュメントデータベースにすることができます
- PostgreSQLとMongoDBやCouchbaseデータベースなどのNoSQLデータベースが混在する、2つ以上のデータストアが選択されるアーキテクチャを見てきました。 REST APIは、アプリケーションがデータをさまざまなデータストアにプッシュするのに役立ちます。 JSONをサポートするPostgreSQLを使用すると、1つのデータストアを選択するだけで、このアーキテクチャの複雑さを回避できます。
- PostgreSQLのJSONデータをクエリしてインデックスに登録できるため、驚異的なパフォーマンスとスケーラビリティが実現します
- JSONBデータ型は、ストレージとパフォーマンスに優れているため、最も推奨されるオプションです。全文検索と索引付けを完全にサポートします。優れたパフォーマンスを発揮します
- JSONデータ型は、JSON文字列をJSONとして保存する必要があり、それほど複雑なテキスト検索を実行していない場合にのみ使用してください。
- PostgreSQLにJSONを使用する最大の利点は、SQLを使用して検索を実行できることです
- PostgreSQLでのJSON検索のパフォーマンスは、MongoDBなどの最高のNoSQLデータベースと同等です