Spanner Graph GQL の MATCH ステートメントについて: Part.1 基礎編
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 GRAPH
と DROP PROPERTY GRAPH
という DDL 文が追加されています。
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 に図があるためそのまま引用しますが、ノード、エッジともに全てのプロパティが図に書かれているわけではないことには注意してください。
図にされてみるとプロパティグラフというモデルが理解できるでしょう。少しコメントをしておきます。
- 全てのノードとエッジはラベルとプロパティを持ちます。
- 先ほど
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
から始まる必要があり、 FinGraph
は CREATE 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は Account
と Person
を合わせた全てのノードの数です。
また何もグラフ要素から値は得ていませんが、 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
で埋められます。
ラベル条件
Person
か Account
の片方だけを探したい場合はどうすれば良いでしょうか?ラベル条件を使うことができます。
ラベル条件には :
もしくは 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)
このクエリは name
が Dana
の Person
から 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
の先にある Account
に INNER 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
からの送金を列挙した上で送金していない相手には amount
は NULL
が出力されるようにしたものが次のようなクエリです。
> 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