使用 Spanner Graph 管理无架构数据

本页面介绍了如何在 Spanner Graph 中管理无架构数据。还详细介绍了最佳做法问题排查提示。建议您熟悉 Spanner Graph 架构查询

借助无架构数据管理,您可以灵活地定义图表,在这种情况下,可以添加、更新或删除节点和边缘类型定义,而无需更改架构。此方法支持迭代开发,减少架构管理开销,同时还能保留熟悉的图表查询体验。

无架构数据管理在以下情况下尤为有用:

  • 您可以管理经常更改的图表,例如更新和添加元素标签和属性。
  • 您的图表有许多节点和边缘类型,这使得创建和管理输入表变得具有挑战性。

对无架构数据建模

借助 Spanner Graph,您可以根据表格创建图表,其中行会映射到节点和边缘。无架构数据建模通常使用单个节点表和单个边缘表,其中包含用于标签的 STRING 列和用于属性的 JSON 列,而不是为每种元素类型使用单独的表。

创建输入表

您可以创建单个 GraphNode 表和单个 GraphEdge 表来存储无架构数据,如以下示例所示。表名称仅供说明之用,您可以自行选择。

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id);

CREATE TABLE GraphEdge (
  id INT64 NOT NULL,
  dest_id INT64 NOT NULL,
  edge_id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id, dest_id, edge_id),
  INTERLEAVE IN PARENT GraphNode;

此示例会执行以下操作:

  • 将所有节点存储在单个表 GraphNode 中,并通过 id 进行唯一标识。

  • 将所有边缘存储在单个表 GraphEdge 中,并通过源 (id)、目标 (dest_id) 和其自身标识符 (edge_id) 的组合进行唯一标识。edge_id 包含在主键中,以允许从 iddest_id 对存在多条边缘。

节点表和边缘表都有各自的 labelproperties 列,分别为 STRINGJSON 类型。

创建属性图表

使用 CREATE PROPERTY GRAPH 语句,将上一部分中的输入表映射为节点和边缘。您需要使用以下子句为无架构数据定义标签和属性:

  • DYNAMIC LABEL:通过输入表中的 STRING 列创建节点或边缘的标签。
  • DYNAMIC PROPERTIES:通过输入表中的 JSON 列创建节点或边缘的属性。

以下示例展示了如何使用这些子句创建图表:

CREATE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode(id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode(id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

动态标签

DYNAMIC LABEL 子句指定了一个 STRING 数据类型列,用于存储标签值。

例如,在 GraphNode 行中,如果 label 列具有 person 值,则会映射到图表中的 Person 节点。同样,在 GraphEdge 行中,如果标签列的值为 owns,则会映射到图表中的 Owns 边缘。

将“GraphNode”标签映射到“GraphEdge”标签

动态属性

DYNAMIC PROPERTIES 子句指定了一个 JSON 数据类型列,用于存储属性。JSON 键是属性名称,JSON 值是属性值。

例如,当 GraphNode 行的 properties 列具有 JSON 值 '{"name": "David", "age": 43}' 时,它会映射到具有 agename 属性的节点,且 43"David" 作为属性值。

何时不使用无架构数据管理

在以下情况下,您可能不希望使用无架构数据管理:

  • 图表数据的节点和边缘类型定义良好,或者其标签和属性不需要频繁更新。
  • 您的数据已存储在 Spanner 中,您更希望通过现有表构建图表,而不是引入新的专用节点和边缘表。
  • 无架构数据的限制阻碍了您采用该技术。

此外,如果您的工作负载对写入性能高度敏感(尤其是属性经常更新时),那么使用架构定义的属性(具有 STRINGINT64 等原始数据类型)比使用类型为 JSON 的动态属性更有效。

如需详细了解如何在不使用动态数据标签和属性的情况下定义图表架构,请参阅 Spanner Graph 架构概览

查询无架构图表数据

您可以使用 Graph Query Language (GQL) 查询无架构图表数据。您可以使用 Spanner Graph 查询概览GQL 参考文档中的示例查询,并进行少量修改。

使用标签匹配节点和边缘

您可以使用 GQL 中的标签表达式匹配节点和边缘。

以下查询会匹配标签列中值为 accounttransfers 的已连接节点和边缘。

GRAPH FinGraph
MATCH (a:Account {id: 1})-[t:Transfers]->(d:Account)
RETURN COUNT(*) AS result_count;

访问属性

JSON 数据类型的顶级键和值会作为属性进行建模,例如以下示例中的 agename

JSON document Properties

   {
     "name": "Tom",
     "age": 43,
   }
"name": "Tom"
"age": 34

以下示例展示了如何从 Person 节点访问属性 name

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

这将返回如下所示的结果:

JSON"Tom"

转换属性数据类型

属性会视为 JSON 数据类型的值。在某些情况下(例如与 SQL 类型进行比较),需要先将其转换为 SQL 类型。

在以下示例中,查询会执行以下数据类型转换:

  • is_blocked 属性转换为布尔值类型以评估表达式。
  • order_number_str 属性转换为字符串类型,并将其与字面量值 "302290001255747" 进行比较。
  • 使用 LAX_INT64 函数将 order_number_str 安全地转换为整数作为返回值类型。
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->()
WHERE BOOL(a.is_blocked) AND STRING(t.order_number_str) = "302290001255747"
RETURN LAX_INT64(t.order_number_str) AS order_number_as_int64;

这将返回如下所示的结果:

+-----------------------+
| order_number_as_int64 |
+-----------------------+
| 302290001255747       |
+-----------------------+

GROUP BYORDER BY 等子句中,您还需要转换 JSON 数据类型。以下示例将 city 属性转换为字符串类型,以便用于分组。

GRAPH FinGraph
MATCH (person:Person {country: "South Korea"})
RETURN STRING(person.city) as person_city, COUNT(*) as cnt
LIMIT 10

将 JSON 数据类型转换为 SQL 数据类型的提示:

  • 严格转换器(例如 INT64)会严格检查类型和值。如果已知并强制执行 JSON 数据类型(例如,使用架构限制条件来强制执行属性数据类型),建议使用此方法。
  • 灵活转换器(例如 LAX_INT64)会尽可能安全地转换值,并在转换不可行时返回 NULL。如果不需要严格检查或类型难以强制执行,建议使用此方法。

您可以在问题排查提示中详细了解数据转换。

按属性值过滤

属性过滤条件中,过滤条件参数会视为 JSON 数据类型的值。例如,在以下查询中,is_blocked 会视为 JSON booleanorder_number_str 会视为 JSON string

GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str:"302290001255747"}]->()
RETURN a.id AS account_id;

这将返回如下所示的结果:

+-----------------------+
| account_id            |
+-----------------------+
| 7                     |
+-----------------------+

过滤条件参数必须与属性类型和值匹配。例如,当 order_number_str 过滤条件参数为整数时,由于该属性为 JSON string,因此找不到匹配项。

GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str: 302290001255747}]->()
RETURN t.order_number_str;

访问嵌套 JSON 属性

嵌套 JSON 键和值不会作为属性进行建模。在以下示例中,JSON 键 citystatecountry 未作为属性进行建模,因为它们嵌套在 location 下。不过,您可以使用 JSON 字段访问运算符或 JSON 下标运算符访问它们。

JSON document Properties

   {
     "name": "Tom",
     "age": 43,
     "location": {
       "city": "New York",
       "state": "NY",
       "country": "USA",
     }
   }
"name": "Tom"
"age": 34
"location": {
  "city": "New York",
  "state": "NY",
  "country": "USA",
}

以下示例展示了如何使用 JSON 字段访问运算符访问嵌套属性。

GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN STRING(person.location.city);

这将返回如下所示的结果:

"New York"

修改无架构数据

Spanner Graph 会将表中的数据映射到图表节点和边缘。当您更改输入表数据时,它会直接导致对相应图表数据进行变更。如需详细了解图表数据变更,请参阅插入、更新或删除 Spanner Graph 数据

示例

本部分提供了有关如何创建、更新和删除图表数据的示例。

插入图表数据

以下示例插入 person 节点。标签和属性名称必须使用小写字母

INSERT INTO GraphNode (id, label, properties)
VALUES (4, "person", JSON'{"name": "David", "age": 43}');

更新图表数据

以下示例更新 Account 节点,并使用 JSON_SET 函数设置其 is_blocked 属性。

UPDATE GraphNode
SET properties = JSON_SET(
  properties,
  '$.is_blocked', false
)
WHERE label = "account" AND id = 16;

以下示例使用一组新属性更新 person 节点。

UPDATE GraphNode
SET properties = JSON'{"name": "David", "age": 43}'
WHERE label = "person" AND id = 4;

以下示例使用 JSON_REMOVE 函数从 Account 节点中移除 is_blocked 属性。执行后,所有其他现有属性保持不变。

UPDATE GraphNode
SET properties = JSON_REMOVE(
  properties,
  '$.is_blocked'
)
WHERE label = "account" AND id = 16;

删除图表数据

以下示例删除已转移到被屏蔽账号的 Account 节点上的 Transfers 边缘。

DELETE FROM GraphEdge
WHERE label = "transfers" AND id IN {
  GRAPH FinGraph
  MATCH (a:Account)-[:Transfers]->{1,2}(:Account {is_blocked: TRUE})
  RETURN a.id
}

限制

本部分列出了使用无架构数据管理的限制。

动态标签的单个表要求

如果节点定义中使用了动态标签,您只能有一个节点表。 此限制也适用于边缘表。不允许以下行为:

  • 与任何其他节点表一起定义具有动态标签的节点表。
  • 与任何其他边缘表一起定义具有动态标签的边缘表。
  • 定义多个节点表或多个边缘表,每个表均使用一个动态标签。

例如,当尝试创建具有动态标签的多个图表节点时,以下代码会失败。

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNodeOne
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties),
    GraphNodeTwo
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties),
    Account
      LABEL Account PROPERTIES(create_time)
  )
  EDGE TABLES (
    ...
  );

标签名称必须为小写

标签字符串值必须以小写形式存储,才能进行匹配。我们建议您在应用代码中或使用架构限制条件来强制执行此规则。

虽然标签字符串值必须以小写形式存储,但在查询中引用时,它们不区分大小写。

以下示例展示了如何在小写值中插入标签:

INSERT INTO GraphNode (id, label) VALUES (1, "account");
INSERT INTO GraphNode (id, label) VALUES (2, "account");

您可以使用不区分大小写的标签来匹配 GraphNodeGraphEdge

GRAPH FinGraph
MATCH (accnt:Account {id: 1})-[:Transfers]->(dest_accnt:Account)
RETURN dest_accnt.id;

属性名称必须使用小写字母

属性名称必须以小写形式存储。我们建议您在应用代码中或使用架构限制条件来强制执行此规则。

虽然属性名称必须以小写形式存储,但在查询中引用时不区分大小写。

以下示例使用小写字母插入 nameage 属性。

INSERT INTO GraphNode (id, label, properties)
VALUES (25, "person", JSON '{"name": "Kim", "age": 27}');

在查询文本中,属性名称不区分大小写。例如,您可以使用 Ageage 来访问该属性。

GRAPH FinGraph
MATCH (n:Person {Age: 27})
RETURN n.id;

其他限制

  • 只有 JSON 数据类型的顶级键会作为属性进行建模。
  • 属性数据类型必须符合 Spanner JSON 类型规范

最佳做法

本部分介绍了对无架构数据建模的最佳做法。

节点和边缘的主键定义

节点的键在所有图表节点中都应是唯一的。例如,作为 INT64 或字符串 UUID 列。

如果两个节点之间有多个边缘,您必须为边缘引入唯一标识符。该架构示例使用应用逻辑 INT64 edge_id 列。

为节点表和边缘表创建架构时,您可以选择添加 label 列作为主键列(如果值不可变)。如果您这样做,则由所有键列组成的复合键在所有节点或边缘之间都应是唯一的。此方法可提高仅按标签过滤的查询的性能。

如需详细了解主键选择,请参阅选择主键

频繁访问的属性的二级索引

如需提高过滤条件中常用属性的查询性能,您可以针对生成的属性列创建二级索引,然后在图表架构和查询中使用该二级索引。

以下示例向 person 节点的 GraphNode 表添加生成的 age 列。对于没有 person 标签的节点,该值为 NULL

ALTER TABLE GraphNode
ADD COLUMN person_age INT64 AS
(IF (label = "person", LAX_INT64(properties.age), NULL));

然后,它会为 person_age 创建一个 NULL FILTERED INDEX,并将其插入到 GraphNode 表中以供本地访问。

CREATE NULL_FILTERED INDEX IdxPersonAge
ON GraphNode(id, label, person_age), INTERLEAVE IN GraphNode;

GraphNode 表现在包含可用作图表节点属性的新列。如需在属性图表定义中反映此情况,请使用 CREATE OR REPLACE PROPERTY GRAPH 语句。这会重新编译定义,并添加新的 person_age 列作为属性。

以下语句会重新编译定义,并添加新的 person_age 列作为属性。

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode (id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode (id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

以下示例会运行具有索引属性的查询。

GRAPH FinGraph
MATCH (person:Person {person_age: 43})
RETURN person.id, person.name;

您可以选择在创建索引后运行 ANALYZE 命令,以便使用最新的数据库统计信息更新查询优化器。

检查数据完整性的限制条件

Spanner 支持架构对象(例如检查限制条件),以强制执行标签和属性数据完整性。本部分列出了可与无架构数据搭配使用的检查限制条件的建议。

强制执行有效的标签值

我们建议您在标签列定义中使用 NOT NULL,以避免出现未定义的标签值。

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id);

强制使用小写标签值和属性名称

由于标签和属性名称必须存储为小写值,因此我们建议您执行以下任一操作:

在查询时,标签和属性名称不区分大小写。

以下示例向 GraphNode 表添加节点标签限制条件,以确保标签为小写。

ALTER TABLE GraphNode ADD CONSTRAINT NodeLabelLowerCaseCheck
CHECK(LOWER(label) = label);

以下示例向边缘资源名称添加检查限制条件。该检查使用 JSON_KEYS 访问顶级密钥。如果 JSON_KEYS 返回 NULLCOALESCE 会将输出转换为空数组,然后检查每个键是否为小写。

ALTER TABLE GraphEdge ADD CONSTRAINT EdgePropertiesLowerCaseCheck
CHECK(NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

强制执行属性存在

您可以创建一个限制条件,用于检查标签是否存在属性。

在以下示例中,限制条件检查 person 节点是否具有 name 属性。

ALTER TABLE GraphNode
ADD CONSTRAINT NameMustExistForPersonConstraint
CHECK (IF(label = 'person', properties.name IS NOT NULL, TRUE));

强制执行属性唯一性

您可以创建基于属性的限制条件,以检查节点或边缘的属性在具有相同标签的节点或边缘中是否唯一。为此,请针对属性的生成的列使用唯一索引

在以下示例中,唯一索引会检查 namecountry 属性的组合对于任何 person 节点是否都是唯一的。

  1. PersonName 添加生成的列。

    ALTER TABLE GraphNode
    ADD COLUMN person_name STRING(MAX)
    AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
    
  2. PersonCountry 添加生成的列。

    ALTER TABLE GraphNode
    ADD COLUMN person_country STRING(MAX)
    AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;
    
  3. 针对 PersonNamePersonCountry 属性创建 NULL_FILTERED 唯一索引。

    CREATE UNIQUE NULL_FILTERED INDEX NameAndCountryMustBeUniqueForPerson
    ON GraphNode (person_name, person_country);
    

强制执行属性数据类型

您可以对标签的属性值使用数据类型限制条件来强制执行属性数据类型,如以下示例所示。此示例使用 JSON_TYPE 函数来检查 person 标签的 name 属性是否使用 STRING 类型。

ALTER TABLE GraphNode
ADD CONSTRAINT PersonNameMustBeStringTypeConstraint
CHECK (IF(label = 'person', JSON_TYPE(properties.name) = 'string', TRUE));

将已定义标签与动态标签结合使用

借助 Spanner,属性图表中的节点可以同时具有已定义标签(在架构中定义)和动态标签(从数据中派生)。您可以自定义标签来利用这种灵活性。

请考虑以下显示了如何创建 GraphNode 表的架构:

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      LABEL Entity -- Defined label
      DYNAMIC LABEL (label) -- Dynamic label from data column 'label'
      DYNAMIC PROPERTIES (properties)
  );

在这里,从 GraphNode 创建的每个节点均具有定义的标签 Entity。此外,每个节点都具有一个由其标签列中的值确定的动态标签。

然后,您可以编写查询,以根据任一标签类型匹配节点。例如,以下查询会使用定义的 Entity 标签查找节点:

GRAPH FinGraph
MATCH (node:Entity {id: 1}) -- Querying by the defined label
RETURN node.name;

尽管此查询使用定义的标签 Entity,但请记住匹配的节点还会根据其数据携带动态标签。

架构示例

您可以使用本部分中的架构示例作为模板,以创建自己的架构。关键架构组件包括以下内容:

  • 创建图表输入表
  • 创建属性图表
  • 可选:反向边缘遍历索引,可提升反向遍历性能
  • 可选:标签索引,用于提高按标签查询的性能
  • 可选:架构限制条件,用于强制执行小写标签属性名称

以下示例展示了如何创建输入表和属性图表:

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON
) PRIMARY KEY (id);

CREATE TABLE GraphEdge (
  id INT64 NOT NULL,
  dest_id INT64 NOT NULL,
  edge_id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON
) PRIMARY KEY (id, dest_id, edge_id),
  INTERLEAVE IN PARENT GraphNode;

CREATE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode(id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode(id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

以下示例使用索引来改进反向边缘遍历。STORING (properties) 子句包含边缘属性的副本,这可以加快对这些属性进行过滤的查询的速度。如果您的查询无法从 STORING (properties) 子句中受益,则可以省略该子句。

CREATE INDEX R_EDGE ON GraphEdge (dest_id, id, edge_id) STORING (properties),
INTERLEAVE IN GraphNode;

以下示例使用标签索引来加快按标签匹配节点的速度。

CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);

以下示例添加可强制使用小写标签和属性的限制条件。最后两个示例使用 JSON_KEYS 函数。(可选)您可以在应用逻辑中强制执行小写检查。

ALTER TABLE GraphNode ADD CONSTRAINT node_label_lower_case
CHECK(LOWER(label) = label);

ALTER TABLE GraphEdge ADD CONSTRAINT edge_label_lower_case
CHECK(LOWER(label) = label);

ALTER TABLE GraphNode ADD CONSTRAINT node_property_keys_lower_case
CHECK(
  NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

ALTER TABLE GraphEdge ADD CONSTRAINT edge_property_keys_lower_case
CHECK(
  NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

使用 DML 优化动态属性的批量更新

使用 JSON_SETJSON_REMOVE 等函数修改动态属性涉及读取-修改-写入操作。与更新 STRINGINT64 类型的属性相比,这些属性可能会导致更高的费用。

如果您的工作负载涉及使用 DML 批量更新动态属性,请考虑以下建议以实现更好的性能:

  • 在单个 DML 语句中更新多行,而不是单独处理各行。

  • 更新宽键范围时,按主键对受影响的行进行分组和排序。使用每个 DML 更新非重叠的范围可减少锁定争用。

  • 在 DML 语句中使用查询参数,而不是对其进行硬编码,以提高性能。

根据这些建议,以下示例在单个 DML 语句中为 100 个节点更新 is_blocked 属性。查询参数包括以下内容:

  1. @node_idsGraphNode 行键,存储在 ARRAY 参数中。 如果适用,对它们进行分组并跨 DML 排序可以获得更好的性能。

  2. @is_blocked_values:要更新的相应值,存储在 ARRAY 参数中。

UPDATE GraphNode
SET properties = JSON_SET(
  properties,
  '$.is_blocked',
  CASE id
    WHEN @node_ids[OFFSET(0)] THEN @is_blocked_values[OFFSET(0)]
    WHEN @node_ids[OFFSET(1)] THEN @is_blocked_values[OFFSET(1)]
    ...
    WHEN @node_ids[OFFSET(99)] THEN @is_blocked_values[OFFSET(99)]
  END,
  create_if_missing => TRUE)
WHERE id IN UNNEST(@node_ids)

问题排查

本部分介绍了如何排查无架构数据的问题。

属性在 TO_JSON 结果中出现多次

问题

以下节点会将 birthdayname 属性作为 JSON 列中的动态属性进行建模。图表元素 JSON 结果中显示了 birthdayname 的重复属性。

GRAPH FinGraph
MATCH (n: Person {id: 14})
RETURN SAFE_TO_JSON(n) AS n;

这将返回如下所示的结果:

{
  ,
  "properties": {
    "birthday": "1991-12-21 00:00:00",
    "name": "Alex",
    "id": 14,
    "label": "person",
    "properties": {
      "birthday": "1991-12-21 00:00:00",
      "name": "Alex"
    }
  }
  
}

可能的原因

默认情况下,基表的所有列都被定义为属性。使用 TO_JSONSAFE_TO_JSON 返回图表元素会导致属性重复。这是因为 JSON 列(即 properties)是架构定义的属性,而 JSON 的第一级键是作为动态属性进行建模的。

推荐的解决方案

如需避免此行为,请在架构中定义属性时使用 PROPERTIES ALL COLUMNS EXCEPT 子句来排除 properties 列,如以下示例所示:

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      PROPERTIES ALL COLUMNS EXCEPT (properties)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

架构更改后,返回的 JSON 数据类型的图表元素没有重复项。

GRAPH FinGraph
MATCH (n: Person {id: 1})
RETURN TO_JSON(n) AS n;

此查询会返回以下内容:

{
  
  "properties": {
    "birthday": "1991-12-21 00:00:00",
    "name": "Alex",
    "id": 1,
    "label": "person",
  }
}

属性值未正确转换时的常见问题

以下问题的常见解决方法是在查询表达式内使用属性时始终使用属性值转换。

无需转换的属性值比较

问题

No matching signature for operator = for argument types: JSON, STRING

可能的原因

查询未正确转换属性值。例如,在比较中,name 属性不会转换为 STRING 类型:

GRAPH FinGraph
MATCH (p:Person)
WHERE p.name = "Alex"
RETURN p.id;

推荐的解决方案

如需解决此问题,请在比较之前使用值转换。

GRAPH FinGraph
MATCH (p:Person)
WHERE STRING(p.name) = "Alex"
RETURN p.id;

这将返回如下所示的结果:

+------+
| id   |
+------+
| 1    |
+------+

或者,使用属性过滤条件简化相等比较,其中值转换会自动完成。请注意,值的类型(“Alex”)必须与 JSON 中属性的 STRING 类型完全匹配。

GRAPH FinGraph
MATCH (p:Person {name: 'Alex'})
RETURN p.id;

这将返回如下所示的结果:

+------+
| id   |
+------+
| 1    |
+------+

RETURN DISTINCT 属性值无需转换即可使用

问题

Column order_number_str of type JSON cannot be used in `RETURN DISTINCT

可能的原因

在以下示例中,order_number_strRETURN DISTINCT 语句中使用之前尚未进行转换:

GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT t.order_number_str AS order_number_str;

推荐的解决方案

如需解决此问题,请在 RETURN DISTINCT 之前使用值转换。

GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT STRING(t.order_number_str) AS order_number_str;

这将返回如下所示的结果:

+-----------------+
| order_number_str|
+-----------------+
| 302290001255747 |
| 103650009791820 |
| 304330008004315 |
| 304120005529714 |
+-----------------+

无需转换即可用作分组键的属性

问题

Grouping by expressions of type JSON is not allowed.

可能的原因

在以下示例中,t.order_number_str 在用于对 JSON 对象进行分组之前不会转换:

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN t.order_number_str, COUNT(*) AS total_transfers;

推荐的解决方案

如需解决此问题,请在使用该属性作为分组键之前先进行值转换。

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN STRING(t.order_number_str) AS order_number_str, COUNT(*) AS total_transfers;

这将返回如下所示的结果:

+-----------------+------------------+
| order_number_str | total_transfers |
+-----------------+------------------+
| 302290001255747 |                1 |
| 103650009791820 |                1 |
| 304330008004315 |                1 |
| 304120005529714 |                2 |
+-----------------+------------------+

无需转换即可用作排序键的属性

问题

ORDER BY does not support expressions of type JSON

可能的原因

在以下示例中,t.amount 在用于对结果进行排序之前未进行转换:

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN a.Id AS from_account, b.Id AS to_account, t.amount
ORDER BY t.amount DESC
LIMIT 1;

推荐的解决方案

如需解决此问题,请对 ORDER BY 子句中的 t.amount 进行转换。

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN a.Id AS from_account, b.Id AS to_account, t.amount
ORDER BY DOUBLE(t.amount) DESC
LIMIT 1;

这将返回如下所示的结果:

+--------------+------------+--------+
| from_account | to_account | amount |
+--------------+------------+--------+
|           20 |          7 | 500    |
+--------------+------------+--------+

转换期间类型不匹配

问题

The provided JSON input is not an integer

可能的原因

在以下示例中,order_number_str 属性存储为 JSON STRING 数据类型。如果您尝试将其转换为 INT64,则会返回错误。

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE INT64(e.order_number_str) = 302290001255747
RETURN e.amount;

推荐的解决方案

如需解决此问题,请使用与值类型匹配的确切值转换器。

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE STRING(e.order_number_str) = "302290001255747"
RETURN e.amount;

这将返回如下所示的结果:

+-----------+
| amount    |
+-----------+
| JSON"200" |
+-----------+

或者,如果值可转换为目标类型,请使用灵活转换器,如以下示例所示:

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE LAX_INT64(e.order_number_str) = 302290001255747
RETURN e.amount;

这将返回如下所示的结果:

+-----------+
| amount    |
+-----------+
| JSON"200" |
+-----------+

后续步骤