このページでは、Spanner Graph でスキーマレス データを管理する方法について説明します。また、ベスト プラクティスとトラブルシューティングのヒントについても説明します。Spanner Graph のスキーマとクエリについて理解しておくことをおすすめします。
スキーマレス データ管理では、柔軟なグラフ定義を作成できます。スキーマを変更せずに、ノードとエッジのタイプ定義を追加、更新、削除できます。このアプローチでは、反復的な開発をサポートし、スキーマ管理のオーバーヘッドを削減しながら、使い慣れたグラフクエリの操作性を維持できます。
スキーマレス データ管理は、次のシナリオで役立ちます。
要素のラベルやプロパティの更新や追加など、頻繁に変更されるグラフを管理する。
グラフに多くのノードタイプとエッジタイプがあるため、入力テーブルの作成と管理が煩雑になる。
スキーマレス データ管理を使用するタイミングの詳細については、スキーマレス データ管理の考慮事項をご覧ください。
スキーマレス データをモデル化する
Spanner Graph を使用すると、行をノードとエッジにマッピングするテーブルからグラフを作成できます。通常、スキーマレス データ モデリングでは、要素タイプごとに個別のテーブルを使用する代わりに、単一のノードテーブルと単一のエッジテーブルを使用します。このテーブルには、ラベル用の STRING 列とプロパティ用の JSON 列があります。
入力テーブルを作成する
次の例に示すように、単一の GraphNode テーブルと単一の GraphEdge テーブルを作成して、スキーマレス データを保存できます。テーブル名は説明のみを目的としており、独自の名前を選択できます。
CREATE TABLE GraphNode (
id INT64 NOT NULL,
label STRING(MAX) NOT NULL,
properties JSON,
) PRIMARY KEY (id);
CREATE TABLE GraphEdge (
id INT64 NOT NULL,
dest_id INT64 NOT NULL,
edge_id INT64 NOT NULL,
label STRING(MAX) NOT NULL,
properties JSON,
) PRIMARY KEY (id, dest_id, edge_id),
INTERLEAVE IN PARENT GraphNode;
この例では、次の操作を行います。
すべてのノードを単一のテーブル
GraphNodeに保存します。このテーブルは一意のidで識別されます。すべてのエッジを単一のテーブル
GraphEdgeに保存します。エッジは、ソース(id)、宛先(dest_id)、独自の識別子(edge_id)の一意の組み合わせで識別されます。edge_idは主キーの一部として含まれており、idからdest_idペアへの複数のエッジを許可します。
ノードテーブルとエッジテーブルの両方に、独自の label 列と properties 列があります。これらの列の型は、それぞれ STRING と JSON です。
スキーマレス データ管理のキーの選択の詳細については、ノードとエッジの主キーの定義をご覧ください。
プロパティ グラフを作成する
CREATE PROPERTY GRAPH ステートメントは、前のセクションの入力テーブルをノードとエッジとしてマッピングします。次の句を使用して、スキーマレス データのラベルとプロパティを定義します。
DYNAMIC LABEL: 入力テーブルのSTRING列からノードまたはエッジのラベルを作成します。DYNAMIC PROPERTIES: 入力テーブルのJSON列からノードまたはエッジのプロパティを作成します。
次の例は、これらの句を使用してグラフを作成する方法を示しています。
CREATE PROPERTY GRAPH FinGraph
NODE TABLES (
GraphNode
DYNAMIC LABEL (label)
DYNAMIC PROPERTIES (properties)
)
EDGE TABLES (
GraphEdge
SOURCE KEY (id) REFERENCES GraphNode(id)
DESTINATION KEY (dest_id) REFERENCES GraphNode(id)
DYNAMIC LABEL (label)
DYNAMIC PROPERTIES (properties)
);
動的ラベルを定義する
DYNAMIC LABEL 句は、ラベル値を格納する STRING データ型の列を指定します。
たとえば、GraphNode 行の label 列に person 値が含まれている場合、グラフ内の Person ノードにマッピングされます。同様に、GraphEdge 行で、ラベル列の値が owns の場合、グラフ内の Owns エッジにマッピングされます。
動的ラベルの使用に関する制限事項の詳細については、制限事項をご覧ください。
動的プロパティを定義する
DYNAMIC PROPERTIES 句は、プロパティを格納する JSON データ型の列を指定します。JSON キーはプロパティ名を表し、JSON 値はプロパティ値を表します。
たとえば、GraphNode 行の properties 列に JSON 値 '{"name": "David", "age": 43}' が含まれている場合、Spanner では age プロパティと name プロパティを持つノードにマッピングされ、それぞれの値は 43 と "David" になります。
スキーマレス データ管理の考慮事項
次のシナリオでは、スキーマレス データ管理を使用しないことをおすすめします。
- グラフデータのノードタイプとエッジタイプが明確に定義されているか、ラベルとプロパティを頻繁に更新する必要がない。
- データはすでに Spanner に保存されており、新しい専用のノードテーブルとエッジテーブルを導入するのではなく、既存のテーブルからグラフを構築したい。
- スキーマレス データの制限により、導入が妨げられている。
また、ワークロードが書き込みパフォーマンスの影響を受けやすい場合(特にプロパティが頻繁に更新される場合)、JSON 型の動的プロパティを使用するよりも、STRING や INT64 などのプリミティブ データ型でスキーマ定義プロパティを使用するほうが効果的です。
動的データラベルとプロパティを使用せずにグラフスキーマを定義する方法については、Spanner Graph スキーマの概要をご覧ください。
スキーマレス グラフデータをクエリする
スキーマレスのグラフデータには、Graph Query Language(GQL)を使用してクエリを実行できます。Spanner Graph クエリの概要と GQL リファレンスのサンプルクエリに変更を加えて使用できます。
ラベルを使用してノードとエッジを照合する
GQL のラベル式を使用して、ノードとエッジを照合できます。
次のクエリは、ラベル列に値 account と transfers を持つ接続されたノードとエッジに一致します。
GRAPH FinGraph
MATCH (a:Account {id: 1})-[t:Transfers]->(d:Account)
RETURN COUNT(*) AS result_count;
プロパティにアクセスする
Spanner では、JSON データ型の最上位のキーと値は、次の例の age や name などのプロパティとしてモデル化されます。
JSON document |
Properties |
|
|
|
次の例は、Person ノードからプロパティ name にアクセスする方法を示しています。
GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN person.name;
クエリは次のような結果を返します。
JSON"Tom"
プロパティのデータ型を変換する
Spanner は、プロパティを JSON データ型の値として扱います。SQL 型との比較など、場合によっては、まずプロパティを SQL 型に変換する必要があります。
次の例では、クエリによって次のデータ型変換が実行されます。
is_blockedプロパティをブール値型に変換して式を評価します。order_number_strプロパティを文字列型に変換し、リテラル値"302290001255747"と比較します。- LAX_INT64 関数を使用して、
order_number_strを戻り値の型として安全に整数に変換します。
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->()
WHERE BOOL(a.is_blocked) AND STRING(t.order_number_str) = "302290001255747"
RETURN LAX_INT64(t.order_number_str) AS order_number_as_int64;
これにより、次のような結果が返されます。
+-----------------------+
| order_number_as_int64 |
+-----------------------+
| 302290001255747 |
+-----------------------+
GROUP BY や ORDER BY などの句では、JSON データ型の変換も必要です。次の例では、city プロパティを文字列型に変換して、グループ化に使用できるようにします。
GRAPH FinGraph
MATCH (person:Person {country: "South Korea"})
RETURN STRING(person.city) as person_city, COUNT(*) as cnt
LIMIT 10
JSON データ型を SQL データ型に変換するためのヒント:
- 厳格なコンバータ(
INT64など)は、厳格な型と値のチェックを行います。JSON データ型が既知で適用されている場合は、厳密なコンバータを使用します。たとえば、スキーマ制約を使用してプロパティのデータ型を適用する場合などです。 LAX_INT64などの柔軟なコンバータは、変換が可能であれば値を安全に変換し、変換が不可能な場合はNULLを返します。厳格なチェックが不要な場合や、型を適用しにくい場合は、柔軟なコンバータを使用します。
データ変換の詳細については、トラブルシューティングのヒントをご覧ください。
プロパティ値でフィルタする
プロパティ フィルタでは、Spanner はフィルタ パラメータを JSON データ型の値として扱います。たとえば、次のクエリでは、Spanner は is_blocked を JSON boolean として扱い、order_number_str を JSON string として扱います。
GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str:"302290001255747"}]->()
RETURN a.id AS account_id;
これにより、次のような結果が返されます。
+-----------------------+
| account_id |
+-----------------------+
| 7 |
+-----------------------+
フィルタ パラメータは、プロパティのタイプと値と一致する必要があります。たとえば、order_number_str フィルタ パラメータが整数の場合、プロパティが JSON string であるため、Spanner では一致は見つかりません。
GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str: 302290001255747}]->()
RETURN t.order_number_str;
ネストされた JSON プロパティにアクセスする
Spanner では、ネストされた JSON のキーと値はプロパティとしてモデル化されません。次の例では、JSON キー city、state、country は location の下にネストされているため、プロパティとしてモデル化されていません。ただし、JSON フィールド アクセス演算子または JSON 添字演算子を使用してアクセスできます。
JSON document |
Properties |
|
|
|
|
|
次の例は、JSON フィールドのアクセス演算子を使用して、ネストされたプロパティにアクセスする方法を示しています。
GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN STRING(person.location.city);
これにより、次のような結果が返されます。
"New York"
スキーマレス データを変更する
Spanner Graph は、テーブルのデータをグラフのノードとエッジにマッピングします。入力テーブルデータを変更すると、対応するグラフデータが直接変更されます。グラフデータのミューテーションの詳細については、Spanner Graph データの挿入、更新、削除をご覧ください。
クエリの例
このセクションでは、グラフデータを作成、更新、削除する方法の例を示します。
グラフデータを挿入する
次の例では、person ノードを挿入します。ラベル名とプロパティ名には小文字を使用する必要があります。
INSERT INTO GraphNode (id, label, properties)
VALUES (4, "person", JSON'{"name": "David", "age": 43}');
グラフデータを更新する
次の例では、Account ノードを更新し、JSON_SET 関数を使用して is_blocked プロパティを設定します。
UPDATE GraphNode
SET properties = JSON_SET(
properties,
'$.is_blocked', false
)
WHERE label = "account" AND id = 16;
次の例では、新しいプロパティ セットで person ノードを更新します。
UPDATE GraphNode
SET properties = JSON'{"name": "David", "age": 43}'
WHERE label = "person" AND id = 4;
次の例では、JSON_REMOVE 関数を使用して、Account ノードから is_blocked プロパティを削除します。実行後、他の既存のプロパティは変更されません。
UPDATE GraphNode
SET properties = JSON_REMOVE(
properties,
'$.is_blocked'
)
WHERE label = "account" AND id = 16;
グラフデータを削除する
次の例では、ブロックされたアカウントに転送された Account ノード上の Transfers エッジを削除します。
DELETE FROM GraphEdge
WHERE label = "transfers" AND id IN {
GRAPH FinGraph
MATCH (a:Account)-[:Transfers]->{1,2}(:Account {is_blocked: TRUE})
RETURN a.id
}
既知の制限事項
このセクションでは、スキーマレス データ管理の使用に関する制限事項について説明します。
動的ラベルの単一テーブルの要件
定義で動的ラベルを使用する場合、ノードテーブルは 1 つだけにする必要があります。この制限はエッジテーブルにも適用されます。Spanner では、次の操作は許可されていません。
- 他のノードテーブルと一緒に動的ラベルを持つノードテーブルを定義する。
- 他のエッジテーブルと一緒に動的ラベルを持つエッジテーブルを定義する。
- それぞれ動的ラベルを使用する複数のノードテーブルまたは複数のエッジテーブルを定義する。
たとえば、次のコードは、動的ラベルを持つ複数のグラフノード作成を試みるため失敗します。
CREATE OR REPLACE PROPERTY GRAPH FinGraph
NODE TABLES (
GraphNodeOne
DYNAMIC LABEL (label)
DYNAMIC PROPERTIES (properties),
GraphNodeTwo
DYNAMIC LABEL (label)
DYNAMIC PROPERTIES (properties),
Account
LABEL Account PROPERTIES(create_time)
)
EDGE TABLES (
...
);
ラベル名は小文字にする必要がある
ラベルの文字列値を照合するには、小文字で保存する必要があります。このルールは、アプリケーション コードで適用するか、スキーマの制約を使用して適用することをおすすめします。
ラベルの文字列値は小文字で保存する必要がありますが、クエリで参照する場合は大文字と小文字は区別されません。
次の例は、小文字の値でラベルを挿入する方法を示しています。
INSERT INTO GraphNode (id, label) VALUES (1, "account");
INSERT INTO GraphNode (id, label) VALUES (2, "account");
大文字と小文字を区別しないラベルを使用して、GraphNode または GraphEdge を照合できます。
GRAPH FinGraph
MATCH (accnt:Account {id: 1})-[:Transfers]->(dest_accnt:Account)
RETURN dest_accnt.id;
プロパティ名は小文字にする必要がある
プロパティ名は小文字で保存する必要があります。このルールは、アプリケーション コードで適用するか、スキーマの制約を使用して適用することをおすすめします。
プロパティ名は小文字で保存する必要がありますが、クエリで参照する場合は大文字と小文字は区別されません。
次の例では、小文字を使用して name プロパティと age プロパティを挿入します。
INSERT INTO GraphNode (id, label, properties)
VALUES (25, "person", JSON '{"name": "Kim", "age": 27}');
クエリテキストでは、プロパティ名の大文字と小文字は区別されません。たとえば、Age または age を使用してプロパティにアクセスできます。
GRAPH FinGraph
MATCH (n:Person {Age: 27})
RETURN n.id;
その他の制限
スキーマレス データのベスト プラクティス
このセクションでは、スキーマレス データをモデル化するうえで有用なベスト プラクティスについて説明します。
ノードとエッジの主キーを定義する
ノードのキーは、すべてのグラフノードで一意である必要があります。たとえば、INT64 列または文字列 UUID 列として指定します。
2 つのノード間に複数のエッジがある場合は、エッジの一意の ID を導入します。スキーマの例では、アプリケーション ロジックの INT64 edge_id 列を使用しています。
ノードテーブルとエッジテーブルのスキーマを作成するときに、値が不変の場合は、必要に応じて label 列を主キー列として含めます。この場合、すべてのキー列で構成される複合キーは、すべてのノードまたはエッジで一意である必要があります。この手法により、ラベルでのみフィルタリングされるクエリのパフォーマンスが向上します。
主キーの選択の詳細については、主キーを選択するをご覧ください。
アクセス頻度の高いプロパティのセカンダリ インデックスを作成する
フィルタで頻繁に使用されるプロパティのクエリ パフォーマンスを向上させるには、生成されたプロパティ列に対してセカンダリ インデックスを作成します。次に、グラフスキーマとクエリで使用します。
次の例は、生成された age 列を person ノードの GraphNode テーブルに追加する方法を示しています。person ラベルのないノードの場合、値は NULL です。
ALTER TABLE GraphNode
ADD COLUMN person_age INT64 AS
(IF (label = "person", LAX_INT64(properties.age), NULL));
次の DDL ステートメントは、person_age の NULL FILTERED INDEX を作成し、ローカル アクセス用に GraphNode テーブルにインターリーブします。
CREATE NULL_FILTERED INDEX IdxPersonAge
ON GraphNode(id, label, person_age), INTERLEAVE IN GraphNode;
GraphNode テーブルに、グラフノード プロパティとして使用できる新しい列が追加されます。これをプロパティ グラフの定義に反映するには、CREATE OR
REPLACE PROPERTY GRAPH ステートメントを使用します。これにより、定義が再コンパイルされ、新しい person_age 列がプロパティとして含まれます。
詳細については、既存のノード定義またはエッジ定義の更新をご覧ください。
次のステートメントは定義を再コンパイルし、新しい person_age 列をプロパティとして含めます。
CREATE OR REPLACE PROPERTY GRAPH FinGraph
NODE TABLES (
GraphNode
DYNAMIC LABEL (label)
DYNAMIC PROPERTIES (properties)
)
EDGE TABLES (
GraphEdge
SOURCE KEY (id) REFERENCES GraphNode (id)
DESTINATION KEY (dest_id) REFERENCES GraphNode (id)
DYNAMIC LABEL (label)
DYNAMIC PROPERTIES (properties)
);
次の例では、インデックス付きプロパティを使用してクエリを実行します。
GRAPH FinGraph
MATCH (person:Person {person_age: 43})
RETURN person.id, person.name;
必要に応じて、インデックスの作成後に ANALYZE コマンドを実行して、クエリ オプティマイザーが最新のデータベース統計情報で更新されるようにします。
データの整合性のチェック制約を使用する
Spanner は、チェック制約などのスキーマ オブジェクトをサポートしています。これにより、ラベルとプロパティのデータの整合性を確保します。このセクションでは、スキーマレス データで使用できるチェック制約の推奨事項について説明します。
ラベル値を適用する
未定義のラベル値を回避するため、ラベル列の定義で NOT NULL を使用することをおすすめします。
CREATE TABLE GraphNode (
id INT64 NOT NULL,
label STRING(MAX) NOT NULL,
properties JSON,
) PRIMARY KEY (id);
ラベル値とプロパティ名を小文字にすることを必須にする
ラベル名とプロパティ名は小文字の値として保存する必要があるため、次のいずれかを行います。
- アプリケーション ロジックでチェックを適用します。
- スキーマにチェック制約を作成します。
クエリ時、ラベル名とプロパティ名では大文字と小文字が区別されません。
次の例では、ノードラベル制約を GraphNode テーブルに追加して、ラベルが小文字であることを確認します。
ALTER TABLE GraphNode ADD CONSTRAINT NodeLabelLowerCaseCheck
CHECK(LOWER(label) = label);
次の例は、エッジ プロパティ名にチェック制約を追加する方法を示しています。このチェックは JSON_KEYS を使用してトップレベルのキーにアクセスします。COALESCE は、JSON_KEYS が NULL を返した場合、出力を空の配列に変換し、各キーが小文字であることを確認します。
ALTER TABLE GraphEdge ADD CONSTRAINT EdgePropertiesLowerCaseCheck
CHECK(NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));
プロパティが存在することを必須にする
ラベルにプロパティが存在するかどうかを確認する制約を作成します。
次の例では、制約は person ノードに name プロパティがあるかどうかを確認します。
ALTER TABLE GraphNode
ADD CONSTRAINT NameMustExistForPersonConstraint
CHECK (IF(label = 'person', properties.name IS NOT NULL, TRUE));
一意のプロパティを必須にする
プロパティベースの制約を作成して、ノードまたはエッジのプロパティが同じラベルを持つノードまたはエッジ全体で一意かどうかを確認します。これを行うには、プロパティの生成された列に対して UNIQUE INDEX を使用します。
次の例では、一意のインデックスが、name プロパティと country プロパティの組み合わせがすべての person ノードに対して一意であることを確認します。
PersonNameの生成列を追加します。ALTER TABLE GraphNode ADD COLUMN person_name STRING(MAX) AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;PersonCountryの生成列を追加します。ALTER TABLE GraphNode ADD COLUMN person_country STRING(MAX) AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;PersonNameプロパティとPersonCountryプロパティに対してNULL_FILTERED一意のインデックスを作成します。CREATE UNIQUE NULL_FILTERED INDEX NameAndCountryMustBeUniqueForPerson ON GraphNode (person_name, person_country);
プロパティのデータ型を必須にする
次の例に示すように、ラベルのプロパティ値にデータ型の制約を使用して、プロパティのデータ型を必須にします。この例では、JSON_TYPE 関数を使用して、person ラベルの name プロパティが STRING 型を使用していることを確認します。
ALTER TABLE GraphNode
ADD CONSTRAINT PersonNameMustBeStringTypeConstraint
CHECK (IF(label = 'person', JSON_TYPE(properties.name) = 'string', TRUE));
定義済みラベルと動的ラベルを組み合わせる
Spanner では、プロパティ グラフ内のノードに、定義済みラベル(スキーマで定義)と動的ラベル(データから派生)の両方を設定できます。この柔軟性を活用するには、ラベルをカスタマイズします。
GraphNode テーブルの作成を示す次のスキーマについて考えてみましょう。
CREATE OR REPLACE PROPERTY GRAPH FinGraph
NODE TABLES (
GraphNode
LABEL Entity -- Defined label
DYNAMIC LABEL (label) -- Dynamic label from data column 'label'
DYNAMIC PROPERTIES (properties)
);
ここで、GraphNode から作成されたすべてのノードには、定義済みのラベル Entity があります。また、各ノードには、ラベル列の値によって決定される動的ラベルがあります。
その後、いずれかのラベルタイプに基づいてノードを照合するクエリを作成します。たとえば、次のクエリは、定義された Entity ラベルを使用してノードを検索します。
GRAPH FinGraph
MATCH (node:Entity {id: 1}) -- Querying by the defined label
RETURN node.name;
このクエリでは定義済みのラベル Entity を使用していますが、一致したノードにはデータに基づく動的ラベルも付与されます。
スキーマの例
このセクションのスキーマ例をテンプレートとして使用して、独自のスキーマを作成します。主なスキーマ コンポーネントは次のとおりです。
- グラフ入力テーブルの作成
- プロパティ グラフの作成
- 省略可: リバース エッジ トラバース インデックス(リバース トラバースのパフォーマンスを向上させる)
- 省略可: ラベル インデックス(ラベルごとのクエリのパフォーマンスを向上させる)
- 省略可: 小文字のラベルとプロパティ名を強制するスキーマ制約
次の例は、入力テーブルとプロパティ グラフを作成する方法を示しています。
CREATE TABLE GraphNode (
id INT64 NOT NULL,
label STRING(MAX) NOT NULL,
properties JSON
) PRIMARY KEY (id);
CREATE TABLE GraphEdge (
id INT64 NOT NULL,
dest_id INT64 NOT NULL,
edge_id INT64 NOT NULL,
label STRING(MAX) NOT NULL,
properties JSON
) PRIMARY KEY (id, dest_id, edge_id),
INTERLEAVE IN PARENT GraphNode;
CREATE PROPERTY GRAPH FinGraph
NODE TABLES (
GraphNode
DYNAMIC LABEL (label)
DYNAMIC PROPERTIES (properties)
)
EDGE TABLES (
GraphEdge
SOURCE KEY (id) REFERENCES GraphNode(id)
DESTINATION KEY (dest_id) REFERENCES GraphNode(id)
DYNAMIC LABEL (label)
DYNAMIC PROPERTIES (properties)
);
次の例では、インデックスを使用してリバースエッジ トラバーサルを改善しています。STORING (properties) 句にはエッジ プロパティのコピーが含まれており、これらのプロパティでフィルタするクエリの速度が向上します。クエリで STORING (properties) 句を使用しない場合は、省略できます。
CREATE INDEX R_EDGE ON GraphEdge (dest_id, id, edge_id) STORING (properties),
INTERLEAVE IN GraphNode;
次の例では、ラベル インデックスを使用して、ラベルによるノードの照合を高速化します。
CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);
次の例では、小文字のラベルとプロパティを強制する制約を追加します。最後の 2 つの例では、JSON_KEYS 関数を使用しています。必要に応じて、アプリケーション ロジックで小文字チェックを適用できます。
ALTER TABLE GraphNode ADD CONSTRAINT node_label_lower_case
CHECK(LOWER(label) = label);
ALTER TABLE GraphEdge ADD CONSTRAINT edge_label_lower_case
CHECK(LOWER(label) = label);
ALTER TABLE GraphNode ADD CONSTRAINT node_property_keys_lower_case
CHECK(
NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));
ALTER TABLE GraphEdge ADD CONSTRAINT edge_property_keys_lower_case
CHECK(
NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));
DML を使用した動的プロパティのバッチ更新を最適化する
JSON_SET や JSON_REMOVE などの関数を使用して動的プロパティを変更する場合は、読み取り / 変更 / 書き込みオペレーションが実行されます。これにより、STRING 型または INT64 型のプロパティを更新する場合と比較して、費用が増加する可能性があります。
ワークロードに DML を使用した動的プロパティのバッチ更新が含まれている場合は、パフォーマンスを向上させるために次の推奨事項を使用してください。
行を個別に処理するのではなく、1 つの DML ステートメントで複数の行を更新します。
広範なキー範囲を更新する場合は、影響を受ける行を主キーでグループ化して並べ替えます。各 DML で重複しない範囲を更新すると、ロック競合が軽減されます。
ハードコードするのではなく、DML ステートメントでクエリ パラメータを使用して、パフォーマンスを改善します。
これらの候補に基づいて、次の例では、単一の DML ステートメントで 100 個のノードの is_blocked プロパティを更新する方法を示します。クエリ パラメータには次のようなものがあります。
@node_ids:GraphNode行のキー。ARRAYパラメータに格納されます。必要に応じて、DML 間でグループ化と並べ替えを行うと、パフォーマンスが向上します。@is_blocked_values: 更新される対応する値。ARRAYパラメータに保存されます。
UPDATE GraphNode
SET properties = JSON_SET(
properties,
'$.is_blocked',
CASE id
WHEN @node_ids[OFFSET(0)] THEN @is_blocked_values[OFFSET(0)]
WHEN @node_ids[OFFSET(1)] THEN @is_blocked_values[OFFSET(1)]
...
WHEN @node_ids[OFFSET(99)] THEN @is_blocked_values[OFFSET(99)]
END,
create_if_missing => TRUE)
WHERE id IN UNNEST(@node_ids)
トラブルシューティング
このセクションでは、スキーマレス データに関する問題のトラブルシューティング方法について説明します。
プロパティが TO_JSON の結果に複数回表示されている
問題
次のノードは、birthday プロパティと name プロパティを JSON 列の動的プロパティとしてモデル化します。グラフ要素の JSON 結果に、birthday と name の重複するプロパティが表示されます。
GRAPH FinGraph
MATCH (n: Person {id: 14})
RETURN SAFE_TO_JSON(n) AS n;
これにより、次のような結果が返されます。
{
…,
"properties": {
"birthday": "1991-12-21 00:00:00",
"name": "Alex",
"id": 14,
"label": "person",
"properties": {
"birthday": "1991-12-21 00:00:00",
"name": "Alex"
}
}
…
}
考えられる原因
デフォルトでは、ベーステーブルのすべての列がプロパティとして定義されます。TO_JSON または SAFE_TO_JSON を使用してグラフ要素を返すと、重複するプロパティが作成されます。これは、JSON 列(properties)がスキーマ定義のプロパティであるのに対し、JSON の第 1 レベルのキーが動的プロパティとしてモデル化されているためです。
おすすめの解決策
この動作を回避するには、次の例に示すように、スキーマでプロパティを定義するときに PROPERTIES ALL COLUMNS EXCEPT 句を使用して properties 列を除外します。
CREATE OR REPLACE PROPERTY GRAPH FinGraph
NODE TABLES (
GraphNode
PROPERTIES ALL COLUMNS EXCEPT (properties)
DYNAMIC LABEL (label)
DYNAMIC PROPERTIES (properties)
);
スキーマ変更後、JSON データ型の返されたグラフ要素に重複はありません。
GRAPH FinGraph
MATCH (n: Person {id: 1})
RETURN TO_JSON(n) AS n;
このクエリは次の結果を返します。
{
…
"properties": {
"birthday": "1991-12-21 00:00:00",
"name": "Alex",
"id": 1,
"label": "person",
}
}
プロパティ値が正しく変換されない場合の一般的な問題
次の問題を解決するには、クエリ式内でプロパティを使用する場合に常にプロパティ値の変換を使用します。
変換せずにプロパティ値を比較する
問題
No matching signature for operator = for argument types: JSON, STRING
考えられる原因
クエリでプロパティ値が正しく変換されていません。たとえば、name プロパティは、比較では STRING 型に変換されません。
GRAPH FinGraph
MATCH (p:Person)
WHERE p.name = "Alex"
RETURN p.id;
おすすめの解決策
この問題を解決するには、比較の前に値の変換を使用します。
GRAPH FinGraph
MATCH (p:Person)
WHERE STRING(p.name) = "Alex"
RETURN p.id;
これにより、次のような結果が返されます。
+------+
| id |
+------+
| 1 |
+------+
または、プロパティ フィルタを使用して、値の変換が自動的に行われるように等価比較を簡素化することもできます。値の型(Alex)は、JSON のプロパティの STRING 型と完全に一致する必要があります。
GRAPH FinGraph
MATCH (p:Person {name: 'Alex'})
RETURN p.id;
これにより、次のような結果が返されます。
+------+
| id |
+------+
| 1 |
+------+
変換せずに RETURN DISTINCT プロパティ値を使用する
問題
Column order_number_str of type JSON cannot be used in `RETURN DISTINCT
考えられる原因
次の例では、order_number_str は RETURN DISTINCT ステートメントで使用される前に変換されていません。
GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT t.order_number_str AS order_number_str;
おすすめの解決策
この問題を解決するには、RETURN DISTINCT の前に値変換を使用します。
GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT STRING(t.order_number_str) AS order_number_str;
これにより、次のような結果が返されます。
+-----------------+
| order_number_str|
+-----------------+
| 302290001255747 |
| 103650009791820 |
| 304330008004315 |
| 304120005529714 |
+-----------------+
変換せずにグループ化キーとして使用されるプロパティ
問題
Grouping by expressions of type JSON is not allowed.
考えられる原因
次の例では、t.order_number_str は JSON オブジェクトのグループ化に使用される前に変換されません。
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN t.order_number_str, COUNT(*) AS total_transfers;
おすすめの解決策
この問題を解決するには、プロパティをグループ化キーとして使用する前に値変換を使用します。
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN STRING(t.order_number_str) AS order_number_str, COUNT(*) AS total_transfers;
これにより、次のような結果が返されます。
+-----------------+------------------+
| order_number_str | total_transfers |
+-----------------+------------------+
| 302290001255747 | 1 |
| 103650009791820 | 1 |
| 304330008004315 | 1 |
| 304120005529714 | 2 |
+-----------------+------------------+
変換せずに並べ替えキーとして使用されるプロパティ
問題
ORDER BY does not support expressions of type JSON
考えられる原因
次の例では、結果の並べ替えに使用される前に t.amount が変換されません。
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN a.Id AS from_account, b.Id AS to_account, t.amount
ORDER BY t.amount DESC
LIMIT 1;
おすすめの解決策
この問題を解決するには、ORDER BY 句で t.amount を変換します。
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN a.Id AS from_account, b.Id AS to_account, t.amount
ORDER BY DOUBLE(t.amount) DESC
LIMIT 1;
これにより、次のような結果が返されます。
+--------------+------------+--------+
| from_account | to_account | amount |
+--------------+------------+--------+
| 20 | 7 | 500 |
+--------------+------------+--------+
変換時の型の不一致
問題
The provided JSON input is not an integer
考えられる原因
次の例では、order_number_str プロパティは JSON STRING データ型として保存されます。INT64 への変換を試みると、エラーが返されます。
GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE INT64(e.order_number_str) = 302290001255747
RETURN e.amount;
おすすめの解決策
この問題を解決するには、値の型に一致する正確な値変換ツールを使用します。
GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE STRING(e.order_number_str) = "302290001255747"
RETURN e.amount;
これにより、次のような結果が返されます。
+-----------+
| amount |
+-----------+
| JSON"200" |
+-----------+
または、値がターゲット タイプに変換可能な場合は、柔軟なコンバータを使用します。次の例をご覧ください。
GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE LAX_INT64(e.order_number_str) = 302290001255747
RETURN e.amount;
これにより、次のような結果が返されます。
+-----------+
| amount |
+-----------+
| JSON"200" |
+-----------+
次のステップ
- JSON データの変更と JSON 関数リストで JSON の詳細を確認する。
- Spanner Graph と openCypher を比較する。
- Spanner Graph に移行する。