管理无架构数据

本页面介绍了如何在 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}' 时,Spanner 会将其映射到具有 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;

访问属性

Spanner 会将 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"

转换属性数据类型

Spanner 会将属性视为 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。如果不需要严格检查或难以强制使用类型,请使用灵活转换器。

如需详细了解数据转换,请参阅问题排查提示

按属性值过滤

属性过滤条件中,Spanner 会将过滤条件参数视为 JSON 数据类型的值。例如在以下查询中,Spanner 会将 is_blocked 视为 JSON boolean,并将 order_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,因此 Spanner 找不到匹配项。

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

访问嵌套 JSON 属性

Spanner 不会将嵌套 JSON 键和值作为属性进行建模。在以下示例中,Spanner 不会将 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
}

已知限制

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

动态标签的单一表要求

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

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

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

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;

其他限制

  • Spanner 仅将 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));

以下 DDL 语句随后会为 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" |
+-----------+

后续步骤