Spanner Graph 查询概览

本文档介绍了如何在 Spanner Graph 中查询属性图表。本部分中的示例使用您在设置和查询 Spanner Graph 中创建的图表架构,该架构如下图所示:

Spanner Graph 架构示例。

运行 Spanner Graph 查询

您可以通过以下方式运行 Spanner Graph 查询:

直观呈现 Spanner Graph 查询结果

如果查询以 JSON 格式返回完整节点,您可以在 Spanner Studio 中直观呈现 Spanner Graph 查询结果。如需了解详情,请参阅使用 Spanner Graph 可视化图表

Spanner Graph 查询结构

本部分将详细介绍每个查询组件。

以下示例展示了 Spanner Graph 查询的基本结构。

Spanner Graph 查询结构示例。

借助 Spanner Graph,您可以在一个数据库中创建多个图表。查询首先使用 GRAPH 子句指定目标图表 FinGraph

图表模式匹配

图表模式匹配可在图表中查找特定模式。最基本的模式是元素模式(节点模式和边缘模式),用于匹配图表元素(分别是节点和边缘)。元素模式可以组合成路径模式和更复杂的模式。

节点模式

节点模式是与图中的节点匹配的模式。此模式包含一对匹配的圆括号,其中可选择包含图形模式变量、标签表达式和属性过滤器。

查找所有节点

以下查询会返回图表中的所有节点。变量 n(称为图表模式变量)会绑定到匹配节点。在这种情况下,节点模式会匹配图表中的所有节点。

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

结果

查询会返回 labelid,如下所示:

标签 id
账号 7
账号 16
账号 20
Person 1
Person 2
Person 3

查找具有特定标签的所有节点

以下查询会匹配图中具有 Person 标签的所有节点。该查询会返回匹配的节点的 labelidname 属性。

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

结果

标签 id name
Person 1 Alex
Person 2 Dana
Person 3 Lee

查找与标签表达式匹配的所有节点

您可以创建包含一个或多个逻辑运算符的标签表达式

以下查询会匹配图表中具有 PersonAccount 标签的所有节点。图表模式变量 n 公开的属性集是具有 PersonAccount 标签的节点所公开属性的超集。

GRAPH 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

结果

标签 id 生日 create_time
账号 7 NULL 2020-01-10T14:22:20.222Z
账号 16 NULL 2020-01-28T01:55:09.206Z
账号 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 标签且属性 id 等于 1 的所有节点。

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

结果

标签 id name 生日
Person 1 Alex 1991-12-21T08:00:00Z

您可以使用 WHERE 子句对标签和属性构造更复杂的过滤条件。

以下查询会匹配图表中具有 Person 标签且属性 birthday1990-01-10 之前的所有节点。

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

结果

标签 name 生日
Person Dana 1980-10-31T08:00:00Z
Person Lee 1986-12-07T08:00:00Z

边缘模式

边缘模式用于匹配节点之间的边缘或关系。边缘模式用英文方括号 [] 括起来,并使用符号 --><- 表示方向。

与节点模式类似,图表模式变量用于绑定到匹配边缘元素。

查找具有匹配标签的所有边缘

以下查询会返回图中具有 Transfers 标签的所有边缘。图形模式变量 e 会绑定到匹配的边缘。

GRAPH 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

查找与标签表达式和属性过滤条件匹配的所有边缘

与节点模式类似,边缘模式可以使用标签表达式、属性规范和 WHERE 子句,如以下查询所示。该查询会查找与指定 order_number 匹配的所有标记为 Transfers 的边缘。

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

结果

src_account order_number
7 304120005529714
20 304120005529714

使用任意方向边缘模式查找所有边

虽然 Spanner Graph 中的所有边都是有向的,但您可以在查询中使用 any direction 边缘模式 -[]- 来匹配任意方向的边缘。

以下查询会查找涉及被屏蔽账号的所有资金转移。

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

结果

order_number 金额
304330008004315 300
304120005529714 100
103650009791820 300
302290001255747 200

路径模式

路径模式由交替的节点模式和边缘模式构建而成。

使用路径模式,通过指定的标签和属性过滤条件查找节点的所有路径

以下查询会查找从 Person 所拥有且 id 等于 2 的账号发起的所有针对账号的资金转移。

每个匹配的结果都表示一条路径,该路径从 Person {id: 2} 开始,通过使用 Owns 边缘连接的 Account,使用 Transfers 边缘进入另一个 Account

GRAPH 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

量化路径模式

量化模式允许在指定范围内重复某个模式。

匹配量化边缘模式

以下查询会查找与来源 Account(其中 id 等于 7)相距一到三次资金转移(不包括它本身)的所有目标账号。

以限定符 {1, 3} 为后缀的边缘模式。

GRAPH 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 1 16
7 3 16
7 3 16
7 2 20
7 2 20

上一个示例使用 ARRAY_LENGTH 函数来访问 group variable e。如需了解详情,请参阅访问权限群组变量

示例结果中的某些行会重复,因为同一对 srcdst 账号之间可能存在多条符合相应模式的路径。

匹配量化路径模式

以下查询会查找 Account 节点之间经过被屏蔽的中间账号且包含 1 到 2 条 Transfers 边缘的路径。

带英文圆括号的路径模式会进行量化,并且 WHERE 子句会在英文圆括号中进行使用,以指定重复模式的条件。

GRAPH 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 的访问方式如下所示:

  • 一个图形模式变量,该变量绑定到 WHERE 子句 e.amount > 100 中的单个边缘(量化模式中)。
  • 一个群组变量,该变量绑定到 RETURN 语句的 ARRAY_LENGTH(e) 中的边缘元素数组(量化模式之外)。
  • 一个已绑定到边缘元素数组的群组变量,该数组按量化模式之外的 SUM(e.amount) 进行汇总。这是水平汇总示例。
GRAPH 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 SHORTEST 路径搜索前缀。您只能在整个路径模式之前应用这些前缀,而不能在英文圆括号内应用它们。

使用 ANY 进行匹配

以下查询会查找与给定 Account 节点相距 1 个或 2 个 Transfers 的所有可到达的唯一账号。

ANY 路径搜索前缀可确保在一对唯一的 srcdst Account 节点之间仅返回一条路径。在以下示例中,虽然您可以从来源节点 Account 通过两条不同路径到达具有 {id: 16}Account 节点,但结果仅包含一条路径。

GRAPH 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

图表模式

图表模式由一个或多个以英文逗号 (,) 分隔的路径模式组成。图表模式可以包含 WHERE 子句,该子句可让您访问路径模式中的所有图表模式变量,以构成过滤条件。每个路径模式都会生成一个路径集合。

使用图表模式进行匹配

以下查询可识别超过 200 的交易金额中涉及的中间账号及其所有者,资金通过这些账号从来源账号转移到被屏蔽账号。

以下路径模式构成了该图形模式:

  • 第一种模式会查找使用中间账号将资金从一个账号转移到被屏蔽账号的路径。
  • 第二种模式会查找从账号到其所有者的路径。

变量 interm 充当两个路径模式之间的通用链接,这需要 interm 在两个路径模式中引用相同的元素节点。这会根据 interm 变量创建等值联接操作。

GRAPH 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 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;

下表展示了中间结果如何沿着各个语句进行传递。为简洁起见,仅显示中间结果的部分属性。

Statement 中间结果(缩写)
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 拥有 所有者
{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 语句

返回语句定义要从匹配的模式返回的内容。它可以访问图表模式变量,包含表达式和其他子句(例如 ORDER_BY、GROUP_BY)。请参阅 RETURN 语句

Spanner Graph 不支持以查询结果的形式返回图表元素。如需返回整个图表元素,请使用 TO_JSON 函数SAFE_TO_JSON 函数。在这两个函数中,我们建议您使用 SAFE_TO_JSON

以 JSON 格式返回图表元素

GRAPH 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;
GRAPH FinGraph
MATCH (n:Account {id: 7})
-- Certain fields in the graph elements, such as TOKENLIST, can't be returned
-- in the TO_JSON function. In those cases, use the SAFE_TO_JSON function instead.
RETURN SAFE_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 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

函数和表达式

您可以在 Spanner Graph 查询中使用所有 GoogleSQL 函数(聚合函数和标量函数)、运算符条件表达式。Spanner Graph 还支持图形专用函数和运算符。

内置函数和运算符

GQL 中常用以下函数运算符

  • PROPERTY_EXISTS(n, birthday):返回 n 是否公开 birthday 属性。
  • LABELS(n):返回图表架构中定义的 n 的标签。
  • PROPERTY_NAMES(n):返回 n 的属性名称。
  • TO_JSON(n):返回 JSON 格式的 n。如需了解详情,请参阅 TO_JSON 函数

PROPERTY_EXISTS 谓词、LABELS 函数和 TO_JSON 函数,以及 ARRAY_AGGCONCAT 等其他内置函数。

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

NEXT

RETURN
  LABELS(person) AS labels,
  TO_JSON(person) AS person,
  accounts,
  CONCAT(person.city, ", ", person.country) AS location,
  PROPERTY_EXISTS(person, is_blocked) AS is_blocked_property_exists,
  PROPERTY_EXISTS(person, name) AS name_property_exists
LIMIT 1;

结果

is_blocked_property_exists name_property_exists 标签 账号 地理位置
false true Person ["Vacation Fund"] 澳大利亚阿德莱德 {"identifier":"mUZpbkdyYXBoLlBlcnNvbgB4kQI=","kind":"node","labels":["Person"],"properties":{"birthday":"1991-12-21T08:00:00Z","city":"Adelaide","country":"Australia","id":1,"name":"Alex"}}

子查询

子查询是嵌套在另一个查询中的查询。以下列表列出了 Spanner Graph 子查询规则:

  • 子查询包含在一对英文大括号 {} 内。
  • 子查询可以从前导 GRAPH 子句开始,以指定范围内的图表。指定的图表不需要与外部查询中使用的图表相同。
  • 如果子查询中省略了 GRAPH 子句,则会发生以下情况:
    • 范围内的图表是从最接近的外部查询上下文推理出来的。
    • 子查询必须从具有 MATCH. 的图表模式匹配语句开始
  • 在子查询范围外部声明的图形模式变量无法在子查询内再次声明,但可以在子查询内的表达式或函数中引用该变量。

使用子查询查找每个账号进行的资金转移总次数

以下查询演示了如何使用 VALUE 子查询。子查询用 {} 括起来,前缀为 VALUE 关键字。查询会返回从账号发起的转账总次数。

GRAPH FinGraph
MATCH (p:Person)-[:Owns]->(account:Account)
RETURN p.name, account.id AS account_id, VALUE {
  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

如需查看支持的子查询表达式列表,请参阅 Spanner Graph 子查询

查询参数

您可以使用参数查询 Spanner Graph。如需了解详情,请参阅 Spanner 客户端库中的语法,并了解如何使用参数查询数据

以下查询演示了如何使用查询参数。

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

同时查询图表和表

您可以将图表查询与 SQL 结合使用,以便在单个语句中同时访问图表和表中的信息。

GRAPH_TABLE

GRAPH_TABLE 运算符接受线性图表查询,并以表格形式返回其结果,该结果可以无缝集成到 SQL 查询中。这种互操作性可让您使用非图表内容丰富图表查询结果,反之亦然。

例如,您可以创建一个 CreditReports 表并插入一些信用报告,如以下示例所示:

CREATE TABLE CreditReports (
  person_id     INT64 NOT NULL,
  create_time   TIMESTAMP NOT NULL,
  score         INT64 NOT NULL,
) PRIMARY KEY (person_id, create_time);
INSERT INTO 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(
  FinGraph
  MATCH (person:Person)-[:Owns]->(:Account)-[:Transfers]->(account:Account {is_blocked:true})
  RETURN DISTINCT person
) AS gt
JOIN CreditReports AS credit
  ON gt.person.id = credit.person_id
ORDER BY credit.create_time;

结果:

person_id latest_credit_score
1 700
2 800

后续步骤

了解对查询进行调优的最佳实践