グラフクエリの概要

このドキュメントでは、グラフ クエリ言語(GQL)の概要と、BigQuery Graph のグラフ クエリの作成方法について説明します。グラフクエリを実行して、パターンを見つけたり、関係を走査したり、プロパティ グラフから分析情報を取得できます。このドキュメントの例では、人物、所有するアカウント、アカウント間の転送の関係を示す FinGraph というグラフを参照しています。グラフの定義については、FinGraph の例をご覧ください。

クエリの構造

グラフクエリは、グラフの名前と 1 つ以上のリニアクエリ ステートメントで構成されます。各線形クエリには 1 つ以上のステートメントが含まれています。これにより、グラフデータを操作してパターン マッチングを見つけ、変数を定義し、中間データをフィルタして変換し、結果を返すことができます。グラフクエリは、BigQuery で SQL クエリを実行するのと同じ方法で実行します。

グラフクエリ構造の例。
グラフクエリの構造の例。

グラフパターン マッチング

グラフパターン マッチングによって、グラフ内の特定のパターンが検出されます。最も基本的なパターンは要素パターンです。たとえば、ノードと一致するノードパターンや、エッジと一致するエッジパターンなどがあります。

ノードパターン

ノードパターンは、グラフ内のノードと一致します。このパターンには、一致するかっこが含まれています。必要に応じて、グラフパターン変数、ラベル式、プロパティ フィルタを含めることができます。

すべてのノードを検索する

次のクエリは、グラフ内のすべてのノードを返します。グラフパターン変数と呼ばれる変数 n は、一致するノードにバインドされます。この場合、ノードパターンはグラフ内のすべてのノードと一致します。

GRAPH graph_db.FinGraph
MATCH (n)
RETURN LABELS(n) AS label, n.id;

このクエリは labelid を返します。

label id
Account 7
Account 16
Account 20
Person 1
Person 2
Person 3

特定のラベルを持つすべてのノードを検索する

次のクエリは、Person ラベルを持つグラフ内のすべてのノードと一致します。このクエリは、一致したノードのラベルと一部のプロパティを返します。

GRAPH graph_db.FinGraph
MATCH (p:Person)
RETURN LABELS(p) AS label, p.id, p.name;

このクエリは、一致したノードの次のプロパティを返します。

label id name
Person 1 Alex
Person 2 Dana
Person 3 Lee

ラベル式に一致するすべてのノードを検索する

1 つ以上の論理演算子を使用してラベル式を作成できます。たとえば、次のクエリは、Person ラベルまたは Account ラベルを持つグラフ内のすべてのノードと一致します。グラフパターン変数 n は、Person または Account ラベルを持つノードのすべてのプロパティを公開します。

GRAPH graph_db.FinGraph
MATCH (n:Person|Account)
RETURN LABELS(n) AS label, n.id, n.birthday, n.create_time;

このクエリの結果では、次の点に注意してください。

  • すべてのノードに id プロパティがあります。
  • Account ラベルに一致するノードには create_time プロパティがありますが、birthday プロパティはありません。これらのノードの birthday プロパティは NULL です。
  • Person ラベルに一致するノードには birthday プロパティがありますが、create_time プロパティはありません。これらのノードの create_time プロパティは NULL です。
label id birthday create_time
Account 7 NULL 2020-01-10T14:22:20.222Z
Account 16 NULL 2020-01-28T01:55:09.206Z
Account 20 NULL 2020-02-18T13:44:20.655Z
Person 1 1991-12-21T08:00:00Z NULL
Person 2 1980-10-31T08:00:00Z NULL
Person 3 1986-12-07T08:00:00Z NULL

ラベル式とプロパティ フィルタに一致するすべてのノードを検索する

次のクエリは、Person ラベルが付いていて、プロパティ id1 に等しいグラフ内のすべてのノードと一致します。

GRAPH graph_db.FinGraph
MATCH (p:Person {id: 1})
RETURN LABELS(p) AS label, p.id, p.name, p.birthday;

次のような結果になります。

label id name birthday
Person 1 Alex 1991-12-21T08:00:00Z

WHERE 句を使用すると、ラベルとプロパティに対してより複雑なフィルタ条件を作成できます。

次のクエリでは、WHERE 句を使用して、プロパティ birthday1990-01-10 の前にあるノードをフィルタします。

GRAPH graph_db.FinGraph
MATCH (p:Person WHERE p.birthday < '1990-01-10')
RETURN LABELS(p) AS label, p.name, p.birthday;

次のような結果になります。

label name birthday
Person Dana 1980-10-31T08:00:00Z
Person Lee 1986-12-07T08:00:00Z

エッジパターン

エッジパターンは、ノード間のエッジまたは関係に一致します。エッジパターンは角かっこ([])で囲まれ、記号 --><- などを含み、方向を示します。エッジパターンには、一致するエッジにバインドするグラフパターン変数を必要に応じて含めることができます。

一致するラベルを持つすべてのエッジを検索する

このクエリは、Transfers ラベルを持つグラフ内のすべてのエッジを返します。クエリは、グラフパターン変数 e を一致するエッジにバインドします。

GRAPH graph_db.FinGraph
MATCH -[e:Transfers]->
RETURN e.Id as src_account, e.order_number;

次のような結果になります。

src_account order_number
7 304330008004315
7 304120005529714
16 103650009791820
20 304120005529714
20 302290001255747

ラベル式とプロパティ フィルタに一致するすべてのエッジを検索する

次のクエリのエッジパターンは、ラベル式とプロパティ フィルタを使用して、指定された注文番号に一致する Transfers のラベルが付いたすべてのエッジを検索します。

GRAPH graph_db.FinGraph
MATCH -[e:Transfers {order_number: "304120005529714"}]->
RETURN e.Id AS src_account, e.order_number;

次のような結果になります。

src_account order_number
7 304120005529714
20 304120005529714

任意の方向のエッジパターンを使用してすべてのエッジを検索する

クエリで any direction エッジパターン(-[]-)を使用すると、エッジをどちらの方向であっても照合できます。次のクエリは、ブロックされた口座を含むすべての送金を検索します。

GRAPH graph_db.FinGraph
MATCH (account:Account)-[transfer:Transfers]-(:Account {is_blocked:true})
RETURN transfer.order_number, transfer.amount;

次のような結果になります。

order_number amount
304330008004315 300
304120005529714 100
103650009791820 300
302290001255747 200

パスパターン

パスパターンは、ノードパターンとエッジパターンを交互に組み合わせて作成されます。

パスパターンを使用して、特定のノードからのすべてのパスを検索する

次のクエリは、id2 に等しい Person が所有する口座から開始された、ある口座へのすべての送金を見つけます。

一致した各結果は、id2 に等しい場合の Person ノードから、Owns エッジを使用して接続された Account ノードを経由し、Transfers エッジを使用して別の Account ノードに至るパスを表します。

GRAPH graph_db.FinGraph
MATCH
  (p:Person {id: 2})-[:Owns]->(account:Account)-[t:Transfers]->
  (to_account:Account)
RETURN
  p.id AS sender_id, account.id AS from_id, to_account.id AS to_id;

次のような結果になります。

sender_id from_id to_id
2 20 7
2 20 16

定量化されたパスパターン

定量化されたパターンは、指定された範囲内でパターンを繰り返します。

定量化されたエッジパターンを照合する

可変長のパスを見つけるには、エッジパターンに数量子を適用します。次のクエリは、id の値が 7 の送金元 Account から 1 ~ 3 回送金した送金先口座を検索することで、これを示しています。

このクエリは、数量子 {1, 3} をエッジパターン -[e:Transfers]-> に適用します。これにより、Transfers エッジパターンを 1 回、2 回、または 3 回繰り返すパスに一致するようにクエリが指示されます。WHERE 句は、結果から移行元アカウントを除外するために使用されます。ARRAY_LENGTH 関数は、group variable e にアクセスするために使用されます。

GRAPH graph_db.FinGraph
MATCH (src:Account {id: 7})-[e:Transfers]->{1, 3}(dst:Account)
WHERE src != dst
RETURN src.id AS src_account_id, ARRAY_LENGTH(e) AS path_length, dst.id AS dst_account_id;

次のような結果になります。

src_account_id path_length dst_account_id
7 1 16
7 1 16
7 3 16
7 3 16
7 2 20
7 2 20

結果の一部が重複しています。これは、同じソースノードと宛先ノードの間にパターンに一致する複数のパスが存在する可能性があり、クエリがそれらをすべて返すためです。

定量化されたパスパターンを照合する

次のクエリは、ブロックされている中間口座を経由する 1~2 つの Transfers エッジを持つ Account ノード間のパスを見つけます。

かっこで囲まれたパスパターンは定量化され、その WHERE 句で繰り返しパターンの条件を指定します。

GRAPH graph_db.FinGraph
MATCH
  (src:Account)
  ((a:Account)-[:Transfers]->(b:Account {is_blocked:true}) WHERE a != b){1,2}
    -[:Transfers]->(dst:Account)
RETURN src.id AS src_account_id, dst.id AS dst_account_id;

次のような結果になります。

src_account_id dst_account_id
7 20
7 20
20 20

変数をグループ化する

定量化されたパターンで宣言されたグラフパターン変数は、そのパターンの外部でアクセスされるとグループ変数になります。次に、一致するグラフ要素の配列にバインドします。

グループ変数には配列としてアクセスできます。グラフ要素は、一致したパスに沿って出現順に保持されます。グループ変数は、水平集計を使用して集計できます。

グループ変数にアクセスする

次の例では、変数 e は次のようにアクセスされます。

  • 定量化されたパターン内にある場合、WHEREe.amount > 100 の単一エッジにバインドされたグラフパターン変数。
  • 定量化されたパターンの外部にある場合、RETURN ステートメントの ARRAY_LENGTH(e) のエッジ要素配列にバインドされたグループ変数。
  • エッジ要素の配列にバインドされたグループ変数。これは、定量化されたパターンの外部で SUM(e.amount) で集計されます。これは水平集計の例です。
GRAPH graph_db.FinGraph
MATCH
  (src:Account {id: 7})-[e:Transfers WHERE e.amount > 100]->{0,2}
  (dst:Account)
WHERE src.id != dst.id
LET total_amount = SUM(e.amount)
RETURN
  src.id AS src_account_id, ARRAY_LENGTH(e) AS path_length,
  total_amount, dst.id AS dst_account_id;

次のような結果になります。

src_account_id path_length total_amount dst_account_id
7 1 300 16
7 2 600 20

パス検索の接頭辞

ソースノードと宛先ノードを共有するグループ内で一致するパスを制限するには、ANYANY SHORTESTANY CHEAPEST のいずれかのパス検索接頭辞を使用します。これらの接頭辞は、パスパターン全体の前にのみ適用できます。かっこ内には適用できません。

ANY を使用して照合する

次のクエリは、特定の Account ノードから 1 つまたは 2 つの Transfers 離れた、到達可能な一意の口座をすべて検索します。

ANY パス検索接頭辞を使用すると、クエリは固有の src ノードと dst Account ノードのペア間のパスを 1 つだけ返します。次の例では、ソース Account ノードから 2 つの異なるパスで {id: 16} を使用して Account ノードに到達できますが、結果として 1 つのパスのみが返されます。

GRAPH graph_db.FinGraph
MATCH ANY (src:Account {id: 7})-[e:Transfers]->{1,2}(dst:Account)
LET ids_in_path = ARRAY_CONCAT(ARRAY_AGG(e.Id), [dst.Id])
RETURN src.id AS src_account_id, dst.id AS dst_account_id, ids_in_path;

次のような結果になります。

src_account_id dst_account_id ids_in_path
7 16 7,16
7 20 7,16,20

ANY SHORTEST を使用して照合する

ANY SHORTEST パス検索接頭辞を使用すると、エッジ数が最小のパスから、送信元ノードと宛先ノードの各ペアのパスが 1 つずつ選択されて返されます。

たとえば、次のクエリは、id 値が 7Account ノードと id 値が 20Account ノード間の最短パスの一つを見つけます。このクエリでは、1 ~ 3 個の Transfers エッジを持つパスが考慮されます。

GRAPH graph_db.FinGraph
MATCH ANY SHORTEST (src:Account {id: 7})-[e:Transfers]->{1, 3}(dst:Account {id: 20})
RETURN src.id AS src_account_id, dst.id AS dst_account_id, ARRAY_LENGTH(e) AS path_length;

次のような結果になります。

src_account_id dst_account_id path_length
7 20 2

ANY CHEAPEST を使用して照合する

パス検索の接頭辞 ANY CHEAPEST を使用すると、送信元アカウントと宛先アカウントの組み合わせに対して、合計コンピューティング費用が最小のパスが 1 つだけ返されます。

次のクエリは、Account ノード間の合計コンピューティング コストが最小のパスを見つけます。この費用は、Transfers エッジの amount プロパティの合計に基づいています。検索では、1~3 個の Transfers エッジを持つパスが考慮されます。

GRAPH graph_db.FinGraph
MATCH ANY CHEAPEST (src:Account)-[e:Transfers COST e.amount]->{1,3}(dst:Account)
LET total_cost = sum(e.amount)
RETURN src.id AS src_account_id, dst.id AS dst_account_id, total_cost;

次のような結果になります。

src_account_id dst_account_id total_cost
7 7 900
7 16 100
7 20 400
16 7 800
16 16 500
16 20 300
20 7 500
20 16 200
20 20 500

グラフパターン

グラフパターンは、カンマ(,)で区切られた 1 つ以上のパスパターンで構成されます。グラフパターンには WHERE 句を含めることが可能です。これにより、パスパターン内のすべてのグラフパターン変数にアクセスして、フィルタリング条件を形成できます。各パスパターンは、パスのコレクションを生成します。

グラフパターンを使用して照合する

次のクエリは、送信元口座からブロックされた口座に送金される、200 を超える金額の取引に関与する仲介口座とその所有者を特定します。

次のパスパターンがグラフパターンを形成します。

  • 最初のパターンは、中間アカウントを使用してある口座からブロック中の口座に送金が発生するパスを検索します。
  • 2 つ目のパターンは、口座からその所有者までのパスを検索します。

変数 interm は、2 つのパスパターンの間の共通リンクとして機能します。そのため、interm は両方のパスパターンで同じ要素ノードを参照する必要があります。これにより、interm 変数に基づく等結合オペレーションが作成されます。

GRAPH graph_db.FinGraph
MATCH
  (src:Account)-[t1:Transfers]->(interm:Account)-[t2:Transfers]->(dst:Account),
  (interm)<-[:Owns]-(p:Person)
WHERE dst.is_blocked = TRUE AND t1.amount > 200 AND t2.amount > 200
RETURN
  src.id AS src_account_id, dst.id AS dst_account_id,
  interm.id AS interm_account_id, p.id AS owner_id;

次のような結果になります。

src_account_id dst_account_id interm_account_id owner_id
20 16 7 1

リニアクエリ ステートメント

複数のグラフ ステートメントを連結して、リニアクエリ ステートメントを作成できます。このステートメントは、クエリに表示されている順序で実行されます。

  • 各ステートメントは、前のステートメントの出力を入力として受け取ります。最初のステートメントの入力は空です。

  • 最後のステートメントの出力が最終結果です。

たとえば、線形クエリ ステートメントを使用して、ブロック中の口座への最大送金額を確認できます。次のクエリは、ブロックされた口座への送金額が最も大きい口座とその所有者を検索します。

GRAPH graph_db.FinGraph
MATCH (src_account:Account)-[transfer:Transfers]->(dst_account:Account {is_blocked:true})
ORDER BY transfer.amount DESC
LIMIT 1
MATCH (src_account:Account)<-[owns:Owns]-(owner:Person)
RETURN src_account.id AS account_id, owner.name AS owner_name;

次の表に、このプロセスと各ステートメント間で渡される中間結果を示します。簡潔にするため、一部のプロパティのみを示しています。

ステートメント 中間結果(省略)
MATCH
  (src_account:Account)
    -[transfer:Transfers]->
  (dst_account:Account {is_blocked:true})
src_account transfer dst_account
{id: 7} {amount: 300.0} {id: 16, is_blocked: true}
{id: 7} {amount: 100.0} {id: 16, is_blocked: true}
{id: 20} {amount: 200.0} {id: 16, is_blocked: true}

ORDER BY transfer.amount DESC
src_account transfer dst_account
{id: 7} {amount: 300.0} {id: 16, is_blocked: true}
{id: 20} {amount: 200.0} {id: 16, is_blocked: true}
{id: 7} {amount: 100.0} {id: 16, is_blocked: true}

LIMIT 1
src_account transfer dst_account
{id: 7} {amount: 300.0} {id: 16, is_blocked: true}

MATCH
  (src_account:Account)
    <-[owns:Owns]-
  (owner:Person)
src_account transfer dst_account owns owner
{id: 7} {amount: 300.0} {id: 16, is_blocked: true} {person_id: 1, account_id: 7} {id: 1, name: Alex}
RETURN
  src_account.id AS account_id,
  owner.name AS owner_name
        
account_id owner_name
7 Alex

次のような結果になります。

account_id owner_name
7 Alex

RETURN ステートメント

RETURN ステートメントには、一致したパターンから返す内容を指定します。グラフパターン変数にアクセスし、ORDER BYGROUP BY などの式やその他の句を含めることができます。

BigQuery Graph では、グラフ要素をクエリ結果として返すことはできません。グラフ要素全体を返すには、TO_JSON 関数を使用します。

グラフ要素を JSON として返す

GRAPH graph_db.FinGraph
MATCH (n:Account {id: 7})
-- Returning a graph element in the final results is NOT allowed. Instead, use
-- the TO_JSON function or explicitly return the graph element's properties.
RETURN TO_JSON(n) AS n;

次のような結果になります。

n
{"identifier":"mUZpbkdyYXBoLkFjY291bnQAeJEO","kind":"node","labels":["Account"],"properties":{"create_time":"2020-01-10T14:22:20.222Z","id":7,"is_blocked":false,"nick_name":"Vacation Fund"}}

NEXT キーワードを使用して大規模なクエリを作成する

NEXT キーワードを使用して、複数のグラフのリニアクエリ ステートメントを連結できます。最初のステートメントは空の入力を受け取り、後続の各ステートメントの出力が次のステートメントの入力になります。

次の例では、複数のグラフのリニア ステートメントを連結して、送金を受け取った回数が最も多い口座の所有者を検索します。同じ変数(account など)を使用して、複数の線形ステートメントで同じグラフ要素を参照できます。

GRAPH graph_db.FinGraph
MATCH (:Account)-[:Transfers]->(account:Account)
RETURN account, COUNT(*) AS num_incoming_transfers
GROUP BY account
ORDER BY num_incoming_transfers DESC
LIMIT 1

NEXT

MATCH (account:Account)<-[:Owns]-(owner:Person)
RETURN account.id AS account_id, owner.name AS owner_name, num_incoming_transfers;

次のような結果になります。

account_id owner_name num_incoming_transfers
16 Lee 3

リニアクエリ ステートメントを集合演算子と組み合わせることもできます。

関数と式

グラフクエリでは、すべての GoogleSQL 関数(集計関数とスカラー関数の両方)、演算子条件式を使用できます。BigQuery Graph は、グラフクエリでのみ使用できる GQL 関数GQL 演算子もサポートしています。

次のクエリには、グラフクエリ内の GQL 関数と SQL 関数、演算子が混在しています。

GRAPH graph_db.FinGraph
MATCH (person:Person)-[o:Owns]->(account:Account)
WHERE person IS SOURCE OF o
RETURN person, ARRAY_AGG(account.nick_name) AS accounts
GROUP BY person

NEXT

RETURN
  LABELS(person) AS labels,
  accounts,
  CONCAT(person.city, ", ", person.country) AS location,
  TO_JSON(person) AS person
LIMIT 1;

次のような結果になります。

ラベル accounts location person
Person ["Vacation Fund"] Adelaide, Australia {"identifier":"mUZpbkdyYXBoLlBlcnNvbgB4kQI=","kind":"node","labels":["Person"],"properties":{"birthday":"1991-12-21T08:00:00Z","city":"Adelaide","country":"Australia","id":1,"name":"Alex"}}

サブクエリ

サブクエリは、別のクエリにネストされたクエリです。グラフクエリ内のサブクエリには次のルールが適用されます。

  • サブクエリは中かっこ {} で囲まれます。
  • サブクエリは、スコープ内のグラフを指定するために、GRAPH 句で始まります。指定されたグラフは、外側のクエリで使用されているグラフと同じである必要はありません。
  • サブクエリのスコープの外部で宣言されたグラフパターン変数は、サブクエリ内で再度宣言することはできませんが、サブクエリ内の式または関数で参照できます。

サブクエリを使用して、各口座からの合計送金数を確認する

次のクエリは、VALUE サブクエリの使用を示しています。サブクエリは、VALUE キーワードの接頭辞が付いた中かっこ {} で囲まれています。このクエリは、口座から開始された送金の合計回数を返します。

GRAPH graph_db.FinGraph
MATCH (p:Person)-[:Owns]->(account:Account)
RETURN p.name, account.id AS account_id, VALUE {
  GRAPH graph_db.FinGraph
  MATCH (a:Account)-[transfer:Transfers]->(:Account)
  WHERE a = account
  RETURN COUNT(transfer) AS num_transfers
} AS num_transfers;

次のような結果になります。

name account_id num_transfers
Alex 7 2
Dana 20 2
Lee 16 1

サポートされているサブクエリ式の一覧については、BigQuery Graph のサブクエリをご覧ください。

クエリ パラメータ

BigQuery Graph にはパラメータを指定してクエリを実行できます。詳細については、構文をご覧ください。また、パラメータ化クエリを実行する方法もご覧ください。

次のクエリは、クエリ パラメータと一致する id 値を持つ Person ノードに一致します。

GRAPH graph_db.FinGraph
MATCH (person:Person {id: @id})
RETURN person.name;

グラフとテーブルを同時にクエリする

グラフクエリと SQL クエリは、GRAPH_TABLE 演算子を使用して組み合わせることができます。

GRAPH_TABLE 演算子はリニアグラフ クエリを受け取り、その結果を表形式で返します。この結果は SQL クエリに統合できます。この相互運用性により、グラフ以外のコンテンツでグラフクエリの結果を拡充できます。その逆も同様です。

たとえば、次の例に示すように、CreditReports テーブルを作成し、いくつかの信用報告書を挿入できます。

CREATE TABLE graph_db.CreditReports (
  person_id     INT64 NOT NULL,
  create_time   TIMESTAMP NOT NULL,
  score         INT64 NOT NULL,
  PRIMARY KEY (person_id, create_time) NOT ENFORCED
);
INSERT INTO graph_db.CreditReports (person_id, create_time, score)
VALUES
  (1,"2020-01-10 06:22:20.222", 700),
  (2,"2020-02-10 06:22:20.222", 800),
  (3,"2020-03-10 06:22:20.222", 750);

次に、GRAPH_TABLE のグラフパターン マッチングで人物を特定し、グラフクエリの結果を CreditReports テーブルと結合してクレジット スコアを取得します。

SELECT
  gt.person.id,
  credit.score AS latest_credit_score
FROM GRAPH_TABLE(
  graph_db.FinGraph
  MATCH (person:Person)-[:Owns]->(:Account)-[:Transfers]->(account:Account {is_blocked:true})
  RETURN DISTINCT person
) AS gt
JOIN graph_db.CreditReports AS credit
  ON gt.person.id = credit.person_id
ORDER BY credit.create_time;

次のような結果になります。

person_id latest_credit_score
1 700
2 800

次のステップ