使用 Spanner Graph 管理无架构数据

本页介绍了如何在 Spanner 图中管理无架构数据。还详细介绍了最佳实践问题排查提示。建议熟悉 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 中,并且您希望根据现有表构建图,而不是引入新的专用节点和边表。
  • 无架构数据的限制阻碍您采用该技术。

此外,如果您的工作负载对写入性能非常敏感(尤其是在频繁更新属性时),与使用 JSON 类型的动态属性相比,使用架构定义的属性(具有 STRINGINT64 等基元数据类型)会更有效。

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

查询无架构图数据

您可以使用图查询语言 (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));

强制执行房源唯一性

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

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

  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" |
+-----------+

后续步骤