PolarDB は、JSON データを扱うための 3 つのデータ型をサポートしています:json、jsonb、jsonpath です。json または jsonb を使用して JSON ドキュメントを格納し、jsonpath を使用してそれらのドキュメントに対するクエリを表現します。ほとんどのワークロードでは、クエリの処理が速く、インデックス作成をサポートしているため、jsonb の方が適しています。
json と jsonb の選択
どちらの型も同じ入力値を受け入れますが、データの格納方法が異なります:
json | jsonb | |
|---|---|---|
| ストレージ形式 | 入力テキストの正確なコピー | 分解されたバイナリ形式 |
| 入力速度 | より高速 | やや低速 (変換オーバーヘッドあり) |
| クエリ速度 | 低速 (実行ごとに再解析) | 大幅に高速 (再解析なし) |
| インデックス作成 | サポートされていません | サポートされています (GIN、B ツリー、ハッシュ) |
| 空白 | 保持されます | 保持されません |
| キーの順序 | 保持されます | 保持されません |
| 重複キー | すべて保持されます。最後の値が有効になります | 最後の値のみ保持されます |
ほとんどのアプリケーションでは jsonb を使用してください。オブジェクトのキーの順序に関するレガシーな想定を満たすなど、キーの順序や空白を入力どおりに正確に保持する必要がある場合にのみ json を使用します。
JSON プリミティブ型と PostgreSQL 型
PolarDB は、JSON データを格納する際に、JSON プリミティブ型を PostgreSQL 型にマッピングします:
| JSON プリミティブ型 | PostgreSQL 型 | 注 |
|---|---|---|
string | text | \u0000 は許可されません。データベースのエンコーディングで利用できない文字の Unicode エスケープも許可されません |
number | numeric | NaN と infinity は許可されません |
boolean | boolean | 小文字の true と false のみ受け入れられます |
null | (なし) | SQL の NULL は異なる概念です |
jsonb は、PostgreSQL の numeric 型の範囲外の数値を拒否します。json はこのチェックを実行しません。
JSON 文字列は、RFC 7159 で指定されているように UTF-8 でエンコードする必要があります。json型は、データベースのエンコーディングに関係なく Unicode エスケープを許可し、構文の正しさ (\uの後の 4 桁の 16 進数) のみをチェックします。jsonb型はより厳格です:データベースのエンコーディングで表現できない文字の Unicode エスケープを許可せず、\u0000を拒否し、正しい Unicode サロゲートペアを要求します。有効な Unicode エスケープは、格納時に同等の単一文字に変換されます。
入出力構文
JSON の入出力構文は RFC 7159 に従います。以下はすべて有効な json (または jsonb) の式です:
-- スカラー/プリミティブ値
-- プリミティブ値は、数値、引用符で囲まれた文字列、true、false、または null です
SELECT '5'::json;
-- 0 個以上の要素を持つ配列 (要素は同じ型である必要はありません)
SELECT '[1, 2, "foo", null]'::json;
-- キー/値のペアを含むオブジェクト
-- オブジェクトのキーは常に引用符で囲まれた文字列でなければなりません
SELECT '{"bar": "baz", "balance": 7.77, "active": false}'::json;
-- 任意にネストされた配列とオブジェクト
SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::json;json は入力テキストをそのまま出力します。jsonb はそれを正規化します。例:
SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::json;
-- json
-- -------------------------------------------------
-- {"bar": "baz", "balance": 7.77, "active":false}
SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::jsonb;
-- jsonb
-- --------------------------------------------------
-- {"bar": "baz", "active": false, "balance": 7.77}jsonb は数値も正規化します:E 表記で入力された値は、それなしで出力されます。
SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
-- json | jsonb
-- -----------------------+-------------------------
-- {"reading": 1.230e-5} | {"reading": 0.00001230}末尾の小数部のゼロは、意味的には重要ではありませんが、jsonb の出力では保持されます。
JSON ドキュメントの設計
JSON の柔軟なスキーマは、要件が頻繁に変更される場合に役立ちますが、予測可能な構造を持つドキュメントの方がクエリは容易です。構造が強制されていない場合でも、一貫性を維持することで、テーブル内のドキュメントのセットを要約しやすくなります。
JSON ドキュメントは小さく保ってください。行の更新は行全体に行レベルロックを取得するため、大きなドキュメントはロック競合を増加させます。各ドキュメントを、それ以上合理的に細分化できないアトミックな単位として構造化してください。
jsonb のコンテインメントと存在
jsonb は、ネストされたデータをクエリするためのコンテインメント (@>) 演算子と存在 (?) 演算子をサポートしています。これらの演算子には、json に相当するものはありません。
コンテインメント
@> 演算子は、ある jsonb ドキュメントが別のドキュメントに含まれているかどうかをテストします。含まれるオブジェクトは、構造とデータにおいて包含するオブジェクトと一致する必要がありますが、包含するオブジェクト内の一致しない配列要素やキー/値のペアは無視されます。配列要素の順序は重要ではなく、重複する配列要素は一度だけ考慮されます。
-- 単純なスカラー値は、同一の値のみを含みます
SELECT '"foo"'::jsonb @> '"foo"'::jsonb; -- true
-- 右側の配列は左側の配列に含まれています
SELECT '[1, 2, 3]'::jsonb @> '[1, 3]'::jsonb; -- true
-- 配列要素の順序は重要ではありません
SELECT '[1, 2, 3]'::jsonb @> '[3, 1]'::jsonb; -- true
-- 重複する配列要素は問題になりません
SELECT '[1, 2, 3]'::jsonb @> '[1, 2, 2]'::jsonb; -- true
-- 右側の単一ペアのオブジェクトは左側のオブジェクトに含まれています
SELECT '{"product": "PostgreSQL", "version": 9.4, "jsonb": true}'::jsonb @> '{"version": 9.4}'::jsonb; -- true
-- ネストされた配列は、外側のレベルでは含まれているとは見なされません
SELECT '[1, 2, [1, 3]]'::jsonb @> '[1, 3]'::jsonb; -- false
-- しかし、ネストレベルを一致させると機能します
SELECT '[1, 2, [1, 3]]'::jsonb @> '[[1, 3]]'::jsonb; -- true
-- コンテインメントはオブジェクトのネスト境界を越えません
SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"bar": "baz"}'::jsonb; -- false
-- 空のオブジェクトを持つトップレベルのキーは含まれています
SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"foo": {}}'::jsonb; -- true特別な例外として、配列はプリミティブ値を含むことができます (逆はできません):
-- 配列はプリミティブな文字列値を含みます
SELECT '["foo", "bar"]'::jsonb @> '"bar"'::jsonb; -- true
-- 非コンテインメント:プリミティブは配列を含みません
SELECT '"bar"'::jsonb @> '["bar"]'::jsonb; -- falseコンテインメントはネストされているため、クエリは明示的なサブオブジェクトの選択をスキップできます。例えば、tags 配列に "term":"paris" と "term":"food" の両方を持つサブオブジェクトが含まれているドキュメントを見つけるには、次のようにします:
SELECT doc->'site_name' FROM websites
WHERE doc @> '{"tags":[{"term":"paris"}, {"term":"food"}]}';存在
? 演算子は、文字列が jsonb 値のトップレベルでオブジェクトキーまたは配列要素として出現するかどうかをテストします。存在はネストされません。
-- 文字列は配列要素として存在します
SELECT '["foo", "bar", "baz"]'::jsonb ? 'bar'; -- true
-- 文字列はオブジェクトキーとして存在します
SELECT '{"foo": "bar"}'::jsonb ? 'foo'; -- true
-- オブジェクトの値はチェックされません
SELECT '{"foo": "bar"}'::jsonb ? 'bar'; -- false
-- 存在はトップレベルでのみ一致します
SELECT '{"foo": {"bar": "baz"}}'::jsonb ? 'bar'; -- false
-- 文字列はプリミティブな JSON 文字列と一致します
SELECT '"foo"'::jsonb ? 'foo'; -- trueJSON オブジェクトは、内部でキー検索用に最適化されており、線形検索を必要としないため、コンテインメントおよび存在テストには配列よりも適しています。
jsonb のインデックス作成
汎用逆引きインデックス (GIN) は、多数の jsonb ドキュメントにわたってキーまたはキー/値のペアを効率的に検索します。パフォーマンス特性が異なる 2 つの演算子クラスが利用可能です。
GIN 演算子クラスの選択
jsonb_ops (デフォルト) | jsonb_path_ops | |
|---|---|---|
| サポートされる演算子 | @>、?、?&、?| | @>、@?、@@ |
| キー存在演算子 | サポート | 非サポート |
| インデックスサイズ | 大きい | 小さい |
| 検索の特異性 | 低い | 高い |
| インデックス化される項目 | キーごと、値ごとに独立した項目 | 値ごとに 1 つの項目 (値 + キーパスのハッシュ) |
| 空の値の構造 | インデックス化される | インデックス化されない (全インデックススキャンが必要) |
クエリが ?、?&、または ?| を使用する場合は jsonb_ops を使用します。クエリが @>、@?、または @@ のみを使用する場合は jsonb_path_ops を使用します。これは、特にクエリされたキーがデータ内に頻繁に出現する場合に、より小さいインデックスとより良い検索の特異性を生み出します。
jsonb_path_ops は、値を含まない JSON 構造 (例:{"a": {}}) のインデックスエントリを作成しません。このような構造のクエリには、全インデックススキャンが必要です。
GIN インデックスの作成
デフォルトの演算子クラス (jsonb_ops):
CREATE INDEX idxgin ON api USING gin (jdoc);jsonb_path_ops 演算子クラス:
CREATE INDEX idxginp ON api USING gin (jdoc jsonb_path_ops);サブキーの式インデックス:
CREATE INDEX idxgintags ON api USING gin ((jdoc -> 'tags'));クエリが一貫して特定のキーを対象とする場合は、式インデックスを使用します。jdoc -> 'tags' 式インデックスは、jdoc -> 'tags' ? 'qui' のようなクエリに対して、フルドキュメント GIN インデックスよりも小さく高速です。
クエリの例
テーブル api の jsonb 列 jdoc について:
-- キー "company" の値が "Magnafone" であるドキュメントを検索
-- jdoc の GIN インデックスを使用
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"company": "Magnafone"}';
-- "tags" にキーまたは配列要素 "qui" が含まれるドキュメントを検索
-- jdoc のプレーンな GIN インデックスは使用できません。(jdoc -> 'tags') の式インデックスが必要です
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc -> 'tags' ? 'qui';
-- GIN インデックスをサポートする jsonpath マッチング
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] == "qui"';
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
-- 上記の存在クエリの代替としてのコンテインメント
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qui"]}';GIN インデックスは、@? および @@ 演算子を用いた jsonpath マッチングもサポートしています。これらの演算子を処理する際、GIN インデックスは、jsonpath パターンから accessors_chain = 定数 の形式の句を抽出し、それらの句で言及されているキーと値に基づいて検索を行います。アクセサーチェーンには、.key、[*]、および [index] アクセサーが含まれる場合があります。jsonb_ops 演算子クラスでは、.* および .** アクセサーもサポートされていますが、jsonb_path_ops 演算子クラスではサポートされていません。
B ツリーインデックスとハッシュインデックス
jsonb は、B ツリーおよびハッシュインデックスもサポートしており、主に完全な JSON ドキュメントに対する等価性チェックに使用されます。
jsonb 値の B ツリー順序は:
オブジェクト > 配列 > ブール値 > 数値 > 文字列 > Null
n 個のペアを持つオブジェクト > n - 1 個のペアを持つオブジェクト
n 個の要素を持つ配列 > n - 1 個の要素を持つ配列ペアの数が等しいオブジェクトは、次の順序で比較されます:
キー-1, 値-1, キー-2 ...短いキーは長いキーの前に格納されるため、直感的でない順序付けの結果が生じることがある点に注意してください:
{ "aa": 1, "c": 1} > {"b": 1, "d": 1}要素数が等しい配列は、要素ごとに比較されます:
要素-1, 要素-2 ...プリミティブな JSON 値は、基になる PostgreSQL 型と同じルールを使用して比較されます。文字列は、デフォルトのデータベース照合順序を使用します。
変換
追加の拡張は、手続き型言語で使用するための jsonb の変換を実装します。
jsonb_plperl は信頼された拡張です。現在のデータベースで CREATE 特権を持つスーパーユーザでないユーザーは、これをインストールできます。その他のすべての変換拡張には、スーパーユーザ特権が必要です。
jsonpath 型
jsonpath 型は、PostgreSQL で SQL/JSON パス言語を実装します。この型はパス式の解析済みバイナリ表現を格納し、パスエンジンがこれを使用して JSON データから項目を取得します。
構文の規則
SQL/JSON パスのセマンティクスは SQL に従いますが、構文は JavaScript の規則を使用します:
.メンバーへのアクセス[]は配列アクセス用配列は 0 から始まります (1 から始まる SQL 配列とは異なります)
パス式は、シングルクォートで囲まれた SQL 文字列リテラルとして記述されます。値の中のシングルクォートは、二重にする必要があります。パス式内の埋め込み文字列リテラルは、JavaScript/ECMAScript の規則に従います。つまり、ダブルクォートを使用し、バックスラッシュエスケープがサポートされます。
サポートされているエスケープシーケンス: \b、\f、\n、\r、\t、\v(ASCII コントロール文字用); \u*NNNN*(Unicode コードポイント用)。標準の JSON には含まれていないが、以下の 2 つの追加シーケンスもサポートされています: \x*NN*(2 桁の 16 進数字)および \u{*N...*}(1~6 桁の 16 進数字)。
jsonpath 変数
| 変数 | 説明 |
|---|---|
$ | コンテキストアイテム — クエリ対象の JSON 値 |
$varname | 名前付き変数。その値は、JSON処理関数のvars パラメーターによって設定されます。 |
@ | フィルター式におけるパス評価の結果 |
jsonpath アクセサ演算子
| アクセサ | 説明 |
|---|---|
.key / ."$varname" | メンバー アクセサ — 指定されたキーを持つオブジェクト メンバーを返します。キーが $ で始まる場合、または JavaScript 識別子の規則に準拠しない場合は、キーを二重引用符で囲んでください。 |
.* | ワイルドカード メンバー アクセサ — 現在のオブジェクトの最上位レベルにあるすべてのメンバーを返します。 |
.** | 再帰的ワイルドカード メンバー アクセサ — 現在のオブジェクトのすべてのネスト レベルにあるすべてのメンバー値を返します。これは SQL/JSON 標準に対する PostgreSQL の拡張機能です。 |
.**{level} / .**{start_level to end_level} | .** と同様ですが、指定されたネスト レベルに制限されます。レベル 0 は現在のオブジェクトを表します。「last」キーワードを使用すると、最も深いネスト レベルを参照できます。これは SQL/JSON 標準に対する PostgreSQL の拡張機能です。 |
[subscript, ...] | 配列要素アクセサ — 単一要素には index を、スライスには start_index to end_index を使用します。インデックス 0 は先頭要素です。「last」キーワードを使用すると、末尾要素を参照できます。 |
[*] | ワイルドカード 配列要素アクセサ — 配列内のすべての要素を返します。 |
パス式の要素
パス式は、次の要素のシーケンスです:
JSON プリミティブ リテラル: Unicode テキスト、数値、
true、false、またはnullパス変数
アクセサー演算子
jsonpathの演算子とメソッドフィルター式または評価順序を定義するための括弧