管理無結構定義資料

本頁說明如何在 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 配對的多個邊緣。

節點和邊緣資料表都有自己的 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 圖形查詢總覽」和「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 篩選器參數是整數,Spanner 找不到相符項目,因為該屬性是 JSON string

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 圖形資料」。

查詢範例

本節提供範例,說明如何建立、更新及刪除圖形資料。

插入圖表資料

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

如果兩個節點之間有多個邊緣,請為邊緣導入專屬 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));

接著,下列 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 存取頂層鍵。COALESCE 如果 JSON_KEYS 傳回 NULL,就會將輸出內容轉換為空陣列,然後檢查每個鍵是否為小寫。

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

後續步驟