使用 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) 和自身 ID (edge_id) 組合來唯一識別。edge_id 會納入主鍵,以便允許從 iddest_id 的組合有多個邊。

節點和邊資料表都各自有 STRINGJSON 類型的 labelproperties 資料欄。

建立資源圖表

使用 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 圖形查詢總覽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 圖表資料」。

範例

本節提供建立、更新及刪除圖表資料的範例。

插入圖表資料

以下範例會插入 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 資料欄。

如果兩個節點之間有多個邊,您必須為邊引入專屬 ID。結構定義範例使用應用程式邏輯 INT64 edge_id 欄。

建立節點和邊資料表的結構定義時,如果值無法變更,您可以選擇將 label 資料欄納入做為主鍵資料欄。如此一來,由所有鍵欄所組成的複合鍵,在所有節點或邊緣中都應是唯一的。這項技巧可改善只依標籤篩選的查詢效能。

如要進一步瞭解如何選擇主鍵,請參閱「選擇主鍵」。

經常存取的資源的次要索引

如要針對篩選器中常用的屬性提升查詢效能,您可以針對產生的屬性欄建立次要索引,然後在圖形結構定義和查詢中使用該索引。

以下範例會將產生的 age 資料欄新增至 person 節點的 GraphNode 資料表。如果節點沒有 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));

強制執行資源唯一性

您可以建立以屬性為基礎的限制條件,檢查節點或邊的屬性是否在具有相同標籤的節點或邊中不重複。如要這樣做,請針對資源的產生欄使用 UNIQUE INDEX

在以下範例中,不重複索引會檢查 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" |
+-----------+

後續步驟