Spanner Graph 查询概览

本文档介绍了如何在 Spanner 图中查询属性图。本部分中的示例使用您在设置和查询 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
用户 1
用户 2
用户 3

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

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

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

结果

标签 id name
用户 1 Alex
用户 2 Dana
用户 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
用户 1 1991-12-21T08:00:00Z NULL
用户 2 1980-10-31T08:00:00Z NULL
用户 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 生日
用户 1 Alex 1991-12-21T08:00:00Z

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

以下查询会匹配图中具有 Person 标签的所有节点,并且属性 birthday 位于 1990-01-10 之前。

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

结果

标签 name 生日
用户 Dana 1980-10-31T08:00:00Z
用户 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 相距 1 到 3 次转移(除自身以外),且 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 节点之间的路径,这些路径通过一个或两个 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 节点相距一个或两个 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 语句

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 用户 [“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

后续步骤

了解调整查询的最佳实践