📘

Spanner Graph GQL の MATCH ステートメントについて: Part.1 基礎編

2025/02/01に公開

GoogleSQL の pipe syntax と Spanner Graph GQL を比較する で書いた通り、
GQL はプロパティグラフを処理するためのクエリ言語ですが、ほとんどのステートメントは SQL とほぼ同じ結果セットを処理することを役割としています。
その中で、本当の意味でプロパティグラフを処理することができるのが MATCH ステートメントです。

Spanner Graph GQL の解説はまだ少なく、特に MATCH ステートメントは公式ドキュメントを読み込めば理解できるものの、リファレンスまで読まないと理解できないことも多いと言う状況でした。
一応ドキュメントを一通り読んで parser も実装したことがあるので、ここから何回かに続く予定の記事では主に Spanner Graph GQL の MATCH ステートメントについて解説します。

なお、この記事では他のグラフデータベースの知識は不問ですが、基本的な Spanner GoogleSQL は既に理解していることを前提とします。

SQL と GQL は別の言語であるため必ずしも対応関係があるとは限りません。
しかし、 Spanner Graph において GQL は GoogleSQL と完全に同じ基盤の上で動き、一つのクエリ実行の中で混在可能な地続きの処理です。
その一端は Spanner Graph: GoogleSQL expressions = GQL expressions で式やサブクエリがお互いに使えることで示しましたが、この記事でも必要に応じて SQL との対応に触れます。

予備知識: CREATE PROPERTY GRAPH は SQL のテーブルをプロパティグラフにマッピングする

Spanner Graph では CREATE PROPERTY GRAPHDROP PROPERTY GRAPH という DDL 文が追加されています。

https://cloud.google.com/spanner/docs/reference/standard-sql/graph-schema-statements

Spanner Graph の例で全面的に使われる FinGraph プロパティグラフの構築を説明するため、 Set up and query Spanner Graph の ドキュメントから引用します。

CREATE TABLE Person (
  id               INT64 NOT NULL,
  name             STRING(MAX),
  birthday         TIMESTAMP,
  country          STRING(MAX),
  city             STRING(MAX),
) PRIMARY KEY (id);

CREATE TABLE Account (
  id               INT64 NOT NULL,
  create_time      TIMESTAMP,
  is_blocked       BOOL,
  nick_name        STRING(MAX),
) PRIMARY KEY (id);

CREATE TABLE PersonOwnAccount (
  id               INT64 NOT NULL,
  account_id       INT64 NOT NULL,
  create_time      TIMESTAMP,
  FOREIGN KEY (account_id) REFERENCES Account (id)
) PRIMARY KEY (id, account_id),
  INTERLEAVE IN PARENT Person ON DELETE CASCADE;

CREATE TABLE AccountTransferAccount (
  id               INT64 NOT NULL,
  to_id            INT64 NOT NULL,
  amount           FLOAT64,
  create_time      TIMESTAMP NOT NULL,
  order_number     STRING(MAX),
  FOREIGN KEY (to_id) REFERENCES Account (id)
) PRIMARY KEY (id, to_id, create_time),
  INTERLEAVE IN PARENT Account ON DELETE CASCADE;

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    Account,
    Person
  )
  EDGE TABLES (
    PersonOwnAccount
      SOURCE KEY (id) REFERENCES Person (id)
      DESTINATION KEY (account_id) REFERENCES Account (id)
      LABEL Owns,
    AccountTransferAccount
      SOURCE KEY (id) REFERENCES Account (id)
      DESTINATION KEY (to_id) REFERENCES Account (id)
      LABEL Transfers
  );

最後を除くと CREATE TABLE 文であり、全く見慣れた SQL テーブルです。
この普通の SQL テーブルをプロパティグラフとして GQL でクエリ可能な一種のビューとしてマッピングすることが CREATE PROPERTY GRAPH 文の役割です。

上記の DDL の結果は次のようにマッピングされたプロパティグラフです。

SQL table element type label element key (デフォルトでテーブルの PK) source(edgeのみ) destination(edge のみ)
Account ノード Account id(デフォルト)
Person ノード Person id(デフォルト)
PersonOwnAccount エッジ Owns id, account_id (デフォルト) (id) → Person (id) (account_id) → Account (id)
AccountTransferAccount エッジ Transfers id, to_id, create_time (デフォルト) (id) → Account (id) (to_id) → Account (id)

なお、プロパティグラフがプロパティグラフである所以として、ノード、エッジのどちらも単なるグラフの頂点と辺ではなく、ラベルとプロパティ(属性)を持ちます。
デフォルトでは SQL テーブルの全ての列がプロパティとしてマッピングされます。

これを踏まえて FinGraph プロパティグラフの初期データの投入を見ていきましょう。同じドキュメントから引用します。

INSERT INTO Account
  (id, create_time, is_blocked, nick_name)
VALUES
  (7,"2020-01-10 06:22:20.222",false,"Vacation Fund"),
  (16,"2020-01-27 17:55:09.206",true,"Vacation Fund"),
  (20,"2020-02-18 05:44:20.655",false,"Rainy Day Fund");

INSERT INTO Person
  (id, name, birthday, country, city)
VALUES
  (1,"Alex","1991-12-21 00:00:00","Australia","Adelaide"),
  (2,"Dana","1980-10-31 00:00:00","Czech_Republic","Moravia"),
  (3,"Lee","1986-12-07 00:00:00","India","Kollam");

INSERT INTO AccountTransferAccount
  (id, to_id, amount, create_time, order_number)
VALUES
  (7,16,300,"2020-08-29 15:28:58.647","304330008004315"),
  (7,16,100,"2020-10-04 16:55:05.342","304120005529714"),
  (16,20,300,"2020-09-25 02:36:14.926","103650009791820"),
  (20,7,500,"2020-10-04 16:55:05.342","304120005529714"),
  (20,16,200,"2020-10-17 03:59:40.247","302290001255747");

INSERT INTO PersonOwnAccount
  (id, account_id, create_time)
VALUES
  (1,7,"2020-01-10 06:22:20.222"),
  (2,20,"2020-01-27 17:55:09.206"),
  (3,16,"2020-02-18 05:44:20.655");

何の工夫もない INSERT 文であるため解説は必要ないでしょう。 Spanner Graph ではプロパティグラフのデータの更新に必要なものはただの SQL テーブルの更新です。特別な機能は必要ありません。

上の INSERT 文で投入されたデータを FinGraph プロパティグラフの定義を使って解釈すると、グラフ構造が得られます。
Set up and query Spanner Graph に図があるためそのまま引用しますが、ノード、エッジともに全てのプロパティが図に書かれているわけではないことには注意してください。

spanner-graph-example-graph.png

図にされてみるとプロパティグラフというモデルが理解できるでしょう。少しコメントをしておきます。

  • 全てのノードとエッジはラベルとプロパティを持ちます。
    • 先ほど CREATE PROPERTY GRAPH で定義したようにノードのラベルが図中の Account, Person, エッジのラベルが Owns, Transfers です。
      • 他のグラフデータベースではサポートされていないこともありますが、 Spanner Graph では実はラベルを複数持つこともできます。
  • Spanner Graph のエッジは常に source と destination を持つ有向エッジです。無向のエッジはありません。
  • Account ノード間の Transfers エッジを見てわかるように、同じ2つのノード間には任意の向きのエッジが複数存在できます。

CREATE PROPERTY GRAPH は単なるビューなので、 CREATE OR REPLACE PROPERTY GRAPH で定義を変更したり、 DROP PROPERTY GRAPH で削除しても裏のデータには何の影響もありません。
FinGraph 以外の名前で同じテーブルを元にしたプロパティグラフを作成することもできます。

GQL 手習い

ここから実際に Spanner Graph の GQL クエリによるグラフの探索を説明します。なお、 GQL は SQL と同様宣言型言語であるため、実際のアルゴリズムについてはオプティマイザが決定するものであること注意してください。実行計画については後日解説しようと考えています。

なお、ここからの例では下記のようにプロンプトを設定した spanner-mycli を使って解説します。(実行結果に付属するプロファイル情報は省略しています)

$ spanner-mycli -v --set 'CLI_PROMPT=> ' --set 'CLI_PROMPT2=%P'

最小の GQL クエリ

まず、プロパティグラフのデータには実際には触れずに実行可能な最も単純な GQL クエリの基本系を示します。

> GRAPH FinGraph
  RETURN 1 AS n;
+-------+
| n     |
| INT64 |
+-------+
| 1     |
+-------+
1 rows in set (2.2 msecs)

Spanner Graph では常にトップレベルの GQL 文は GRAPH property_graph_name から始まる必要があり、 FinGraphCREATE PROPERTY GRAPH で定義した名前です。
テーブルを一つの定義していなくても FROM なしのクエリを実行可能な SQL とは異なり、 GQL は定義済のプロパティグラフがなければ実行することができません。

RETURN ステートメントはクエリ結果として返す列を指定するため、 SQL の SELECT 列に対応します。
SELECT 1 が可能な SQL とは異なり、無名の列を返すことはできません。

> GRAPH FinGraph
  RETURN 1;
ERROR: spanner: code="InvalidArgument", desc: A name must be explicitly defined for this column [at 2:8]
RETURN 1
       ^

最小のクエリは理解できましたが、GQL はプロパティグラフにアクセスする言語であるため実用上の価値はありません。

GQL パターン

グラフ要素にアクセスする最小のクエリを見ていきましょう。
先ほどのクエリに MATCH ステートメントを追加します。

> GRAPH FinGraph
  MATCH ()
  RETURN 1 AS n;
+-------+
| n     |
| INT64 |
+-------+
| 1     |
| 1     |
| 1     |
| 1     |
| 1     |
| 1     |
+-------+
6 rows in set (4.87 msecs)

RETURN ステートメントで返した1が6行に増えました。この6は何でしょうか。
MATCH ステートメントに書かれた () はプロパティグラフ内の全てのノードにマッチするパターンです。6は AccountPerson を合わせた全てのノードの数です。
また何もグラフ要素から値は得ていませんが、 MATCH ステートメントではパターンにマッチしただけの数の行が生成されます。

MATCH ステートメントに渡すことができる GQL パターンは GQL と SQL/PGQ 両方を標準化している組織がパターンマッチを共通化するために定義した Graph Pattern Matching Language(GPML) と呼ばれる サブ言語であり、多くの機能を持っています。
どれだけ機能があるかというと Spanner Graph の GQL リファレンスにおける GQL patterns のページ は GQL のそれ以外の言語仕様を説明する GQL query statements全体と同じくらいのボリュームがあります(共に印刷時21ページを確認)。

ここからパターンマッチに使われるサブ言語を説明します。

要素パターン(element pattern)

前述のノードパターン () のようにノードやエッジに対して単純にマッチするパターンを要素パターンと呼びます。

ノードパターンとその詳細

先ほどのクエリを拡張し、要素パターンに指定できるものを説明していきます。

さて、グラフから値を得るにはどうすれば良いでしょうか。
パターンにマッチした要素をバインドするグラフパターン変数(graph pattern variable)を用意する必要があります。
ノードにマッチする要素パターンでは () の中に変数名を書く必要があります。 (n) はマッチしたノードを変数 n にバインドします。

次のようなクエリを書くことで FinGraph に存在する全ノードのラベルと id プロパティを得ることができます。

> GRAPH FinGraph
  MATCH (n)
  RETURN LABELS(n) AS labels, n.id;
+---------------+-------+
| labels        | id    |
| ARRAY<STRING> | INT64 |
+---------------+-------+
| [Account]     | 7     |
| [Account]     | 16    |
| [Account]     | 20    |
| [Person]      | 1     |
| [Person]      | 2     |
| [Person]      | 3     |
+---------------+-------+
6 rows in set (5.07 msecs)
  • バインド変数の型は NODE_ELEMENT 型で、 フィールドアクセス演算子(.)でプロパティを得ることができます。
  • NODE_ELEMENT 型に LABELS 関数を適用すると、グラフ要素の全てのラベルを配列で得ることができます。 Spanner Graph ではラベルは複数設定可能です。

上のクエリでは Account, Person 両方に定義されている id プロパティを参照しましたが、片方にしか含まれていないプロパティも参照可能です。

> GRAPH FinGraph
  MATCH (n)
  RETURN LABELS(n) AS labels, n.id, PROPERTY_NAMES(n) AS props, n.nick_name;
+---------------+-------+------------------------------------------+----------------+
| labels        | id    | props                                    | nick_name      |
| ARRAY<STRING> | INT64 | ARRAY<STRING>                            | STRING         |
+---------------+-------+------------------------------------------+----------------+
| [Account]     | 7     | [create_time, id, is_blocked, nick_name] | Vacation Fund  |
| [Account]     | 16    | [create_time, id, is_blocked, nick_name] | Vacation Fund  |
| [Account]     | 20    | [create_time, id, is_blocked, nick_name] | Rainy Day Fund |
| [Person]      | 1     | [birthday, city, country, id, name]      | NULL           |
| [Person]      | 2     | [birthday, city, country, id, name]      | NULL           |
| [Person]      | 3     | [birthday, city, country, id, name]      | NULL           |
+---------------+-------+------------------------------------------+----------------+
6 rows in set (7.08 msecs)

PROPERTY_NAMES 関数で GRAPH_ELEMENT の持つプロパティの名前を取得できます。
nick_name 列は nick_name プロパティを持つ Accountでは値が入っており、 nick_name プロパティを持たない Person では NULL で埋められていることがわかります。このように、他の要素には存在するけれどその要素には含まれないプロパティを参照した場合は NULL で埋められます。

ラベル条件

PersonAccount の片方だけを探したい場合はどうすれば良いでしょうか?ラベル条件を使うことができます。
ラベル条件には : もしくは IS を使うことができます。どちらも同じ意味です。

> GRAPH FinGraph
  MATCH (n:Person)
  RETURN LABELS(n) AS labels, n.id;
+---------------+-------+
| labels        | id    |
| ARRAY<STRING> | INT64 |
+---------------+-------+
| [Person]      | 1     |
| [Person]      | 2     |
| [Person]      | 3     |
+---------------+-------+
3 rows in set (4.77 msecs)
> GRAPH FinGraph
  MATCH (n IS Person)
  RETURN LABELS(n) AS labels, n.id;
+---------------+-------+
| labels        | id    |
| ARRAY<STRING> | INT64 |
+---------------+-------+
| [Person]      | 1     |
| [Person]      | 2     |
| [Person]      | 3     |
+---------------+-------+
3 rows in set (3.91 msecs)

なお、ラベル条件はラベル式を使って AND, OR, NOT の演算を行うことができますし、丸括弧を使って複雑な条件を書くこともできます。
実行しなくても意味は予想できると思いますし、 FinGraph プロパティグラフではラベルの種類が少ないため実行例は割愛します。

MATCH (n:Person&Account) -- AND
MATCH (n:Person|Account) -- OR
MATCH (n:!Person)        -- NOT
MATCH (n:!(A&(B|C))
プロパティへのフィルタ

要素パターンの中でプロパティの値を使ってフィルタする方法が2つあります。
一つ目はプロパティフィルタで、 {name: value, ...} の形で1つ以上のプロパティの値に対して等値条件を書くことができます。

> GRAPH FinGraph
  MATCH (n {nick_name: "Rainy Day Fund"})
  RETURN LABELS(n) AS labels, n.id, n.nick_name;
+---------------+-------+----------------+
| labels        | id    | nick_name      |
| ARRAY<STRING> | INT64 | STRING         |
+---------------+-------+----------------+
| [Account]     | 20    | Rainy Day Fund |
+---------------+-------+----------------+
1 rows in set (47.13 msecs)

このプロパティフィルタは単純ですが、等値比較しかできず関数や式を使うことはできません。 = 相当であるため NULL との比較も期待通りにはいきません。
一般的な条件は SQL でも見慣れた WHERE 句を使います。

> GRAPH FinGraph
  MATCH (n WHERE n.nick_name LIKE "R%")
  RETURN LABELS(n) AS labels, n.id, n.nick_name;
+---------------+-------+----------------+
| labels        | id    | nick_name      |
| ARRAY<STRING> | INT64 | STRING         |
+---------------+-------+----------------+
| [Account]     | 20    | Rainy Day Fund |
+---------------+-------+----------------+
1 rows in set (7.41 msecs)

WHERE 句ではそのパターンでバインドされている変数(ここでは n)を使って任意の条件を書けます。

ここまでのグラフパターン変数、ラベル条件、 WHERE 句とプロパティフィルタそれぞれを任意で組み合わせたものをパターンフィラー(pattern filler)と呼びます。

pattern_filler:
  [ graph_pattern_variable ]
  [ is_label_condition ]
  [ { where_clause | property_filters } ]
エッジパターン

ノードパターンを使ってグラフからノードをクエリする方法については理解できました。エッジはどうでしょうか。
エッジもグラフ要素であるため、同様のパターンが用意されています。

エッジは向きを持つため、エッジフィルタは -[]-> という矢印で表現されます。 [] の中にはノードパターンと同様のパターンフィラーを書けます。

> GRAPH FinGraph
  MATCH -[e:Owns]->
  RETURN LABELS(e) AS labels, PROPERTY_NAMES(e) AS props, e.id, e.account_id;
+---------------+-------------------------------+-------+------------+
| labels        | props                         | id    | account_id |
| ARRAY<STRING> | ARRAY<STRING>                 | INT64 | INT64      |
+---------------+-------------------------------+-------+------------+
| [Owns]        | [account_id, create_time, id] | 1     | 7          |
| [Owns]        | [account_id, create_time, id] | 3     | 16         |
| [Owns]        | [account_id, create_time, id] | 2     | 20         |
+---------------+-------------------------------+-------+------------+
3 rows in set (7.65 msecs)

ノードパターンの最初の例で説明した () のように、グラフパターン変数、ラベル、プロパティフィルタ、 WHERE などのフィラーを一切指定する必要がない場合、エッジパターンでは省略形式を使うことができます。
省略形式では [] が不要となり、 -> のように書くことができます。

> GRAPH FinGraph
  MATCH ->
  RETURN 1 AS n;
+-------+
| n     |
| INT64 |
+-------+
| 1     |
| 1     |
| 1     |
| 1     |
| 1     |
| 1     |
| 1     |
| 1     |
+-------+
8 rows in set (7.52 msecs)

グラフパターン変数を使っていないため値は得られませんが、 FinGraph プロパティグラフに存在する Transfers(5本) と Owns(3本) と を合わせた全てのエッジにマッチしているため、エッジと同数の8行が出力されます。

エッジパターンには右向き、左向き、任意方向の複数の向きと、完全・省略の組み合わせで6種類の記法があります。

完全(full) 省略(abbreviated)
任意方向(any) -[]- -
左向き(left) <-[]- <-
右向き(right) -[]-> ->

これらのパターンについてはエッジパターン単体では説明しづらいため、より複雑な経路パターンへと移りましょう。

経路パターン

グラフはノードとエッジの集合体であり、ノード間がエッジで繋がっていることが重要です。その経路(path)に対してパターンマッチできるのが GQL の本領です。
ノードパターンとエッジパターンを連続して書いたものが基本的な経路パターンです。

> GRAPH FinGraph
  MATCH (p:Person {name: "Dana"})-[:Owns]->(a:Account)
  RETURN p.id AS person_id, p.name AS name, a.id AS account_id, a.nick_name;
+-----------+--------+------------+----------------+
| person_id | name   | account_id | nick_name      |
| INT64     | STRING | INT64      | STRING         |
+-----------+--------+------------+----------------+
| 2         | Dana   | 20         | Rainy Day Fund |
+-----------+--------+------------+----------------+
1 rows in set (10.71 msecs)

このクエリは nameDanaPerson から Owns エッジで結ばれた Account を探し、それぞれを変数に束縛して各プロパティをクエリ結果として出力しています。
ビジネス的な意味を解釈すると Data という人の持つ口座を探すクエリに相当します。

この記事では実行計画には言及しませんが、 Spanner Graph においてはノードとエッジを含む基本的なグラフクエリはプロパティグラフの裏に存在する SQL テーブルをエッジの双方のキーを使ってノードと JOIN した SQL に対応します。

> SELECT p.id AS person_id, p.name AS name, a.id AS account_id, a.nick_name
  FROM Person AS p
  JOIN PersonOwnAccount AS o ON (p.id = o.id)
  JOIN Account AS a ON (a.id = o.account_id)
  WHERE p.name = "Dana";
+-----------+--------+------------+----------------+
| person_id | name   | account_id | nick_name      |
| INT64     | STRING | INT64      | STRING         |
+-----------+--------+------------+----------------+
| 2         | Dana   | 20         | Rainy Day Fund |
+-----------+--------+------------+----------------+
1 rows in set (1.53 msecs)

どの列がノードとエッジのキーであるかというのはプロパティグラフの定義でデータベースが知っているため、キーを一切書かずによくグラフダイアグラムのように矢印を使って書けるというのが GQL の強みです。
この強みは下記のようにより複雑な例になるとわかってくるでしょう。ちなみにこのクエリでは先ほど説明を割愛した左向きの完全エッジパターン(<-[]-)が含まれています。

> GRAPH FinGraph
  MATCH (src:Person {name: "Dana"})-[:Owns]->(:Account)-[transfer:Transfers]->(:Account)<-[:Owns]-(dest:Person)
  RETURN src.name AS src_name, dest.name AS dest_name, transfer.amount;
+----------+-----------+------------+
| src_name | dest_name | amount     |
| STRING   | STRING    | FLOAT64    |
+----------+-----------+------------+
| Dana     | Alex      | 500.000000 |
| Dana     | Lee       | 200.000000 |
+----------+-----------+------------+
2 rows in set (217.62 msecs)

このクエリのビジネス的な意味は Dana という人が持つ口座へ送金した口座を持つ人を探し、総金額と共に出力するクエリでしょうか。
マネーロンダリングの検出はグラフデータベースのユースケースとしてよく例に上がるので、実用的なグラフクエリになってきました。
これと等価なクエリを SQL で書きたくはないですね。

なお、経路パターンにも WHERE 句を書けます。 WHERE 句の条件式ではその経路パターンに含まれるグラフパターン変数全てを使うことができます。
下記のクエリは任意方向のエッジパターン -[]-WHERE 句を使うことで、ブロックされている口座が送金元か送金先のどちらかになっている送金を見つけています。(公式ドキュメント Queries overview より引用)

> GRAPH FinGraph
  MATCH (account:Account)-[transfer:Transfers]-(:Account)
  WHERE account.is_blocked
  RETURN transfer.order_number, transfer.amount;
+-----------------+------------+
| order_number    | amount     |
+-----------------+------------+
| 304330008004315 | 300.000000 |
| 304120005529714 | 100.000000 |
| 103650009791820 | 300.000000 |
| 302290001255747 | 200.000000 |
+-----------------+------------+
4 rows in set (1.19 msecs)
JOIN としての経路パターン

先ほどは経路パターンを連続して書きましたが、分けて書くことも可能です。

前述したこのクエリを分けて書いてみましょう。ここからの例は全て同じ意味になります。

GRAPH FinGraph
MATCH (p:Person {name: "Dana"})-[o:Owns]->(a:Account)
RETURN p.id AS person_id, p.name AS name, a.id AS account_id, a.nick_name;

MATCH ステートメントには複数のグラフパターンを書くことができます。

> GRAPH FinGraph
  MATCH (p:Person {name: "Dana"})-[o:Owns]->, -[o]->(a:Account)
  RETURN p.id AS person_id, p.name AS name, a.id AS account_id, a.nick_name;
+-----------+--------+------------+----------------+
| person_id | name   | account_id | nick_name      |
| INT64     | STRING | INT64      | STRING         |
+-----------+--------+------------+----------------+
| 2         | Dana   | 20         | Rainy Day Fund |
+-----------+--------+------------+----------------+
1 rows in set (23.33 msecs)

また、 MATCH ステートメントを複数書くこともできます。

> GRAPH FinGraph
  MATCH (p:Person {name: "Dana"})-[o:Owns]->
  MATCH -[o]->(a:Account)
  RETURN p.id AS person_id, p.name AS name, a.id AS account_id, a.nick_name;
+-----------+--------+------------+----------------+
| person_id | name   | account_id | nick_name      |
| INT64     | STRING | INT64      | STRING         |
+-----------+--------+------------+----------------+
| 2         | Dana   | 20         | Rainy Day Fund |
+-----------+--------+------------+----------------+
1 rows in set (44.89 msecs)

これらの結果が同じになるのは、グラフパターンマッチ変数が2つの経路パターンで共通して使われているためです。
経路パターンは基本的に JOIN に相当するため、途切れていても Dana から繋がっている Owns の先にある AccountINNER JOIN 相当で結合していることは変わりません。

INNER JOIN 相当以外の JOIN に相当する処理もあります。

先ほどのように複数のパターンを書いた時、それぞれに共通する変数がない場合は CROSS JOIN, デカルト積のように働きます。

> GRAPH FinGraph
  MATCH (p1:Person)
  MATCH (p2:Person)
  RETURN p1.name AS p1_name, p2.name AS p2_name;
+---------+---------+
| p1_name | p2_name |
| STRING  | STRING  |
+---------+---------+
| Alex    | Alex    |
| Alex    | Dana    |
| Alex    | Lee     |
| Dana    | Alex    |
| Dana    | Dana    |
| Dana    | Lee     |
| Lee     | Alex    |
| Lee     | Dana    |
| Lee     | Lee     |
+---------+---------+
9 rows in set (7.7 msecs)

一つの MATCH (p1:Person), (p2:Person) として書いても同様です。

また、 MATCH ステートメントに OPTIONAL をつけることで、 {LEFT|RIGHT} OUTER JOIN 相当を書くこともできます。
先ほどの人から人への送金を探すクエリを少し書き換えて、 Alex からの送金を列挙した上で送金していない相手には amountNULL が出力されるようにしたものが次のようなクエリです。

> GRAPH FinGraph
  MATCH (src:Person {name: "Alex"})-[:Owns]->(src_acc:Account),
        (dest_acc:Account)<-[:Owns]-(dest:Person)
  OPTIONAL MATCH (src_acc)-[transfer:Transfers]->(dest_acc)
  RETURN src.name AS src_name, dest.name AS dest_name, transfer.amount;
+----------+-----------+------------+
| src_name | dest_name | amount     |
| STRING   | STRING    | FLOAT64    |
+----------+-----------+------------+
| Alex     | Lee       | 300.000000 |
| Alex     | Lee       | 100.000000 |
| Alex     | Alex      | NULL       |
| Alex     | Dana      | NULL       |
+----------+-----------+------------+
4 rows in set (38.06 msecs)

Alex から Lee には2回送金されているが、 Alex から Dana (および自分自身) への送金はないことがわかりますね。
このクエリでは一つの MATCH ステートメントにカンマで区切った複数のクエリが書けること、 MATCH ステートメントが複数書けることの両方が活用されています。

まとめ

この記事では GQL の MATCH ステートメントを説明するため、下記の事柄について解説しました。

  • 公式ドキュメントのサンプルに使われる FinGraph スキーマ の CREATE PROPERTY GRAPH
  • GQL グラフパターンの要素パターンと経路パターン

ここまでのパターンでも固定長のグラフ経路に対するパターンマッチを SQL より簡潔に行うことができます。
次の記事からはグラフクエリ言語の本領である、任意長の経路に対するパターンマッチなどの高度な概念を解説します。

Discussion