Schemalose Daten mit Spanner Graph verwalten

Auf dieser Seite wird beschrieben, wie Sie schemalose Daten in Spanner Graph verwalten. Außerdem finden Sie dort Best Practices und Tipps zur Fehlerbehebung. Grundkenntnisse im Umgang mit dem Spanner Graph-Schema und Abfragen werden empfohlen.

Bei der schemalosen Datenverwaltung können Sie eine flexible Definition eines Graphen erstellen, bei der die Knoten- und Kantentypdefinitionen ohne Schemaänderungen hinzugefügt, aktualisiert oder gelöscht werden können. Der Ansatz unterstützt die iterative Entwicklung und einen geringeren Aufwand für die Schemaverwaltung, während die vertraute Graphabfrage beibehalten wird.

Die schemalose Datenverwaltung ist besonders nützlich für die folgenden Szenarien:

  • Sie verwalten Diagramme mit häufigen Änderungen, z. B. Aktualisierungen und Ergänzungen von Elementlabels und -eigenschaften.
  • Ihr Graph enthält viele Knoten- und Kantentypen, was das Erstellen und Verwalten von Eingabetabellen schwierig macht.

Schemalose Daten modellieren

Mit Spanner Graph können Sie ein Diagramm aus Tabellen erstellen, in denen Zeilen Knoten und Kanten zugeordnet werden. Anstatt separate Tabellen für jeden Elementtyp zu verwenden, werden bei der schemalosen Datenmodellierung in der Regel eine einzelne Knotentabelle und eine einzelne Kantentabelle mit einer STRING-Spalte für das Label und einer JSON-Spalte für Eigenschaften verwendet.

Eingabetabellen erstellen

Sie können eine einzelne GraphNode-Tabelle und eine einzelne GraphEdge-Tabelle zum Speichern von schemalosen Daten erstellen, wie im folgenden Beispiel gezeigt. Die Tabellennamen dienen nur zu Veranschaulichung. Sie können eigene Namen verwenden.

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;

Dieses Beispiel tut Folgendes:

  • Alle Knoten werden in einer einzigen Tabelle GraphNode gespeichert, die durch die id eindeutig identifiziert wird.

  • Hier werden alle Kanten in einer einzigen Tabelle GraphEdge gespeichert, die durch die Kombination aus Quelle (id), Ziel (dest_id) und einer eigenen Kennung (edge_id) eindeutig identifiziert wird. edge_id ist Teil des Primärschlüssels, um mehr als eine Kante von einem id-zu-dest_id-Paar zuzulassen.

Sowohl die Knoten- als auch die Kantentabellen haben eigene label- und properties-Spalten vom Typ STRING bzw. JSON.

Property-Graph erstellen

Mit der Anweisung CREATE PROPERTY GRAPH werden die Eingabetabellen im vorherigen Abschnitt als Knoten und Kanten zugeordnet. Sie müssen die folgenden Klauseln verwenden, um Labels und Properties für schemalose Daten zu definieren:

  • DYNAMIC LABEL: Erstellt das Label eines Knotens oder einer Kante aus einer STRING-Spalte der Eingabetabelle.
  • DYNAMIC PROPERTIES: Erstellt Eigenschaften eines Knotens oder einer Kante aus einer JSON-Spalte der Eingabetabelle.

Das folgende Beispiel zeigt, wie Sie mithilfe dieser Klauseln einen Graphen erstellen:

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)
  );

Dynamisches Label

Mit der DYNAMIC LABEL-Klausel wird eine Spalte vom Datentyp STRING zum Speichern der Labelwerte angegeben.

Wenn in einer GraphNode-Zeile beispielsweise die Spalte label den Wert person hat, wird sie einem Person-Knoten im Diagramm zugeordnet. Wenn in einer GraphEdge-Zeile die Labelspalte den Wert owns hat, wird sie ebenfalls einem Owns-Kante im Graphen zugeordnet.

Label „GraphNode“ einem Label „GraphEdge“ zuordnen

Dynamische Properties

Mit der DYNAMIC PROPERTIES-Klausel wird eine Spalte vom Datentyp JSON zum Speichern von Properties angegeben. JSON-Schlüssel sind die Attributnamen und JSON-Werte sind die Attributwerte.

Wenn die Spalte properties einer Zeile GraphNode beispielsweise den JSON-Wert '{"name": "David", "age": 43}' hat, wird sie einem Knoten mit den Eigenschaften age und name mit den Werten 43 und "David" zugeordnet.

Wann die schemalose Datenverwaltung nicht verwendet werden sollte

In den folgenden Fällen sollten Sie die schemalose Datenverwaltung nicht verwenden:

  • Die Knoten- und Kantentypen für Ihre Graphendaten sind klar definiert oder ihre Labels und Properties müssen nicht häufig aktualisiert werden.
  • Ihre Daten werden bereits in Spanner gespeichert und Sie möchten lieber Diagramme aus vorhandenen Tabellen erstellen, anstatt neue Knoten- und Kantentabellen zu verwenden.
  • Die Einschränkungen von schemalosen Daten verhindern eine Nutzung.

Wenn Ihre Arbeitslast sehr schreibleistungsabhängig ist, insbesondere wenn Properties häufig aktualisiert werden, ist die Verwendung von schemadefinierten Properties mit primitiven Datentypen wie STRING oder INT64 effektiver als die Verwendung von dynamischen Properties vom Typ JSON.

Weitere Informationen zum Definieren des Graphschemas ohne dynamische Datenlabels und ‑eigenschaften finden Sie in der Übersicht über Spanner Graph-Schemas.

Schemalose Graphdaten abfragen

Sie können schemalose Graphdaten mit der Graph Query Language (GQL) abfragen. Sie können die Beispielabfragen in der Übersicht über Spanner-Graphabfragen und in der GQL-Referenz mit nur wenigen Änderungen verwenden.

Knoten und Kanten mithilfe von Labels abgleichen

Sie können Knoten und Kanten mit dem Labelausdruck in GQL abgleichen.

Die folgende Abfrage sucht nach verbundenen Knoten und Kanten, die in der Label-Spalte die Werte account und transfers haben.

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

Zugriffseigenschaften

Schlüssel und Werte der obersten Ebene des Datentyps JSON werden als Eigenschaften modelliert, z. B. age und name im folgenden Beispiel.

JSON document Properties

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

Im folgenden Beispiel wird gezeigt, wie über den Knoten Person auf die Property name zugegriffen wird.

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

Dies gibt Ergebnisse wie die folgenden zurück:

JSON"Tom"

Attributdatentypen konvertieren

Eigenschaften werden als Werte des JSON-Datentyps behandelt. In einigen Fällen, z. B. bei Vergleichen mit SQL-Typen, müssen sie zuerst in einen SQL-Typ konvertiert werden.

Im folgenden Beispiel werden in der Abfrage die folgenden Datentypkonvertierungen ausgeführt:

  • Wandelt die Property is_blocked in einen booleschen Typ um, um den Ausdruck zu bewerten.
  • Konvertiert das Attribut order_number_str in einen Stringtyp und vergleicht es mit dem Literalwert "302290001255747".
  • Hier wird die Funktion LAX_INT64 verwendet, um order_number_str sicher in eine Ganzzahl als Rückgabetyp umzuwandeln.
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;

Dies gibt Ergebnisse wie die folgenden zurück:

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

In Klauseln wie GROUP BY und ORDER BY müssen Sie auch den JSON-Datentyp konvertieren. Im folgenden Beispiel wird die Property city in einen Stringtyp umgewandelt, damit sie für die Gruppierung verwendet werden kann.

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

Tipps zum Konvertieren von JSON-Datentypen in SQL-Datentypen:

  • Bei strengen Konvertern wie INT64 werden strenge Typ- und Wertprüfungen durchgeführt. Dies wird empfohlen, wenn der JSON-Datentyp bekannt und erzwungen ist, z. B. wenn Sie Schemaeinschränkungen verwenden, um den Datentyp der Property zu erzwingen.
  • Flexible Konvertierungsfunktionen wie LAX_INT64 konvertieren den Wert nach Möglichkeit sicher und geben NULL zurück, wenn die Umwandlung nicht möglich ist. Dies wird empfohlen, wenn keine strenge Prüfung erforderlich ist oder Typen nur schwer durchgesetzt werden können.

Weitere Informationen zur Datenkonvertierung finden Sie in den Tipps zur Fehlerbehebung.

Nach Attributwerten filtern

In Property-Filtern werden die Filterparameter als Werte des Datentyps JSON behandelt. In der folgenden Abfrage wird is_blocked beispielsweise als JSON-boolean und order_number_str als JSON-string behandelt.

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

Dies gibt Ergebnisse wie die folgenden zurück:

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

Der Filterparameter muss mit dem Property-Typ und dem Property-Wert übereinstimmen. Wenn der Filterparameter order_number_str beispielsweise eine Ganzzahl ist, wird keine Übereinstimmung gefunden, da es sich bei der Property um ein JSON-string handelt.

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

Auf verschachtelte JSON-Properties zugreifen

Verschachtelte JSON-Schlüssel und ‑Werte werden nicht als Properties modelliert. Im folgenden Beispiel werden die JSON-Schlüssel city, state und country nicht als Eigenschaften modelliert, da sie unter location verschachtelt sind. Sie können jedoch mit einem JSON-Operator für den Feldzugriff oder einem JSON-Skriptoperator darauf zugreifen.

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",
}

Das folgende Beispiel zeigt, wie Sie mit dem JSON-Feld Zugriffsoperator auf verschachtelte Properties zugreifen.

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

Dies gibt Ergebnisse wie die folgenden zurück:

"New York"

Schemalose Daten ändern

Spanner Graph ordnet Daten aus Tabellen Graphknoten und -kanten zu. Wenn Sie Daten in der Eingabetabelle ändern, führt dies direkt zu Mutationen der entsprechenden Graphendaten. Weitere Informationen zur Mutation von Graphdaten finden Sie unter Spanner-Graphdaten einfügen, aktualisieren oder löschen.

Beispiele

In diesem Abschnitt finden Sie Beispiele zum Erstellen, Aktualisieren und Löschen von Diagrammdaten.

Diagrammdaten einfügen

Im folgenden Beispiel wird ein person-Knoten eingefügt. Label- und Property-Namen müssen in Kleinbuchstaben geschrieben werden.

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

Diagrammdaten aktualisieren

Im folgenden Beispiel wird ein Account-Knoten aktualisiert und die Funktion JSON_SET wird verwendet, um das Attribut is_blocked festzulegen.

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

Im folgenden Beispiel wird ein person-Knoten mit einer neuen Reihe von Properties aktualisiert.

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

Im folgenden Beispiel wird die Funktion JSON_REMOVE verwendet, um die Eigenschaft is_blocked aus einem Account-Knoten zu entfernen. Nach der Ausführung bleiben alle anderen vorhandenen Properties unverändert.

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

Diagrammdaten löschen

Im folgenden Beispiel wird die Kante Transfers an Account-Knoten gelöscht, die in blockierte Konten übertragen wurden.

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

Beschränkungen

In diesem Abschnitt werden die Einschränkungen der schemalosen Datenverwaltung aufgeführt.

Anforderung an eine einzelne Tabelle für dynamisches Label

Es kann nur eine Knotentabelle geben, wenn in der Definition ein dynamisches Label verwendet wird. Diese Einschränkung gilt auch für die Kantentabelle. Folgendes ist nicht zulässig:

  • Definieren Sie eine Knotentabelle mit einem dynamischen Label neben anderen Knotentabellen.
  • Definition einer Kantentabelle mit einem dynamischen Label neben anderen Kantentabellen.
  • Mehrere Knotentabellen oder Kantentabellen definieren, die jeweils ein dynamisches Label verwenden.

Der folgende Code schlägt beispielsweise fehl, wenn versucht wird, mehrere Graphknoten mit dynamischen Labels zu erstellen.

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 (
    ...
  );

Labelnamen müssen in Kleinbuchstaben geschrieben werden.

Label-Stringwerte müssen in Kleinbuchstaben gespeichert werden, damit sie abgeglichen werden können. Wir empfehlen, diese Regel entweder im Anwendungscode oder mithilfe von Schemaeinschränkungen durchzusetzen.

Label-Stringwerte müssen in Kleinbuchstaben gespeichert werden, bei der Referenzierung in einer Abfrage wird jedoch nicht zwischen Groß- und Kleinschreibung unterschieden.

Im folgenden Beispiel wird gezeigt, wie Sie Labels in Kleinbuchstaben einfügen:

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

Sie können Labels verwenden, bei denen die Groß- und Kleinschreibung ignoriert wird, um GraphNode oder GraphEdge abzugleichen.

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

Property-Namen müssen in Kleinbuchstaben geschrieben sein.

Property-Namen müssen in Kleinbuchstaben gespeichert werden. Wir empfehlen, diese Regel entweder im Anwendungscode oder mithilfe von Schemaeinschränkungen durchzusetzen.

Attributnamen müssen in Kleinbuchstaben gespeichert werden, bei der Bezugnahme in Abfragen wird jedoch nicht zwischen Groß- und Kleinschreibung unterschieden.

Im folgenden Beispiel werden die Properties name und age in Kleinbuchstaben eingefügt.

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

Im Abfragetext wird bei Attributnamen nicht zwischen Groß- und Kleinschreibung unterschieden. Sie können beispielsweise Age oder age verwenden, um auf die Property zuzugreifen.

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

Sonstige Einschränkungen

Best Practices

In diesem Abschnitt werden Best Practices für die Modellierung von schemalosen Daten beschrieben.

Definitionen für Primärschlüssel für Knoten und Kanten

Der Schlüssel eines Knotens muss für alle Knoten des Graphen eindeutig sein. Beispiel: als INT64- oder String-Spalte UUID.

Wenn zwischen zwei Knoten mehrere Kanten vorhanden sind, müssen Sie für die Kante eine eindeutige Kennung angeben. Im Schemabeispiel wird eine Spalte für die Anwendungslogik INT64 edge_id verwendet.

Wenn Sie das Schema für Knoten- und Kantentabellen erstellen, können Sie die Spalte label optional als Primärschlüsselspalte einbeziehen, wenn der Wert unveränderlich ist. In diesem Fall muss der zusammengesetzte Schlüssel, der aus allen Schlüsselspalten gebildet wird, für alle Knoten oder Kanten eindeutig sein. Mit dieser Methode lässt sich die Leistung von Abfragen verbessern, die nur nach Label gefiltert werden.

Weitere Informationen zur Auswahl des Primärschlüssels finden Sie unter Primärschlüssel auswählen.

Sekundärer Index für eine Property, auf die häufig zugegriffen wird

Wenn Sie die Abfrageleistung für eine Eigenschaft, die häufig in Filtern verwendet wird, verbessern möchten, können Sie einen sekundären Index für eine generierte Property-Spalte erstellen und dann im Graphschema und in Abfragen verwenden.

Im folgenden Beispiel wird der Tabelle GraphNode für einen person-Knoten eine generierte Spalte age hinzugefügt. Bei Knoten ohne das Label person ist der Wert NULL.

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

Es wird dann eine NULL FILTERED INDEX für person_age erstellt und in die Tabelle GraphNode für den lokalen Zugriff eingefügt.

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

Die Tabelle GraphNode enthält jetzt neue Spalten, die als Knoteneigenschaften für die Grafik verfügbar sind. Verwenden Sie die Anweisung CREATE OR REPLACE PROPERTY GRAPH, um dies in Ihrer Property-Graph-Definition widerzuspiegeln. Dadurch wird die Definition neu kompiliert und die neue Spalte person_age als Property eingefügt.

Mit der folgenden Anweisung wird die Definition neu kompiliert und die neue Spalte person_age als Property eingefügt.

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)
  );

Im folgenden Beispiel wird eine Abfrage mit der indexierten Property ausgeführt.

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

Optional können Sie nach dem Erstellen des Index den Befehl ANALYZE ausführen, damit der Abfrageoptimierer mit den neuesten Datenbankstatistiken aktualisiert wird.

Einschränkungen für die Datenintegrität prüfen

Spanner unterstützt Schemaobjekte wie Prüfeinschränkungen, um die Integrität von Label- und Property-Daten durchzusetzen. In diesem Abschnitt finden Sie Empfehlungen für Prüfeinschränkungen, die Sie für schemalose Daten verwenden können.

Gültige Labelwerte erzwingen

Wir empfehlen, in der Definition der Labelspalte NOT NULL zu verwenden, um nicht definierte Labelwerte zu vermeiden.

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

Kleinschreibung für Labelwerte und Property-Namen erzwingen

Da Label- und Property-Namen in Kleinbuchstaben gespeichert werden müssen, empfehlen wir Folgendes:

Bei Abfragen wird bei Label- und Attributnamen nicht zwischen Groß- und Kleinschreibung unterschieden.

Im folgenden Beispiel wird der Tabelle GraphNode eine Einschränkung für Knotenlabels hinzugefügt, damit das Label in Kleinbuchstaben geschrieben wird.

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

Im folgenden Beispiel wird dem Namen der Edge-Eigenschaft eine Prüfeinschränkung hinzugefügt. Für die Prüfung wird JSON_KEYS verwendet, um auf die Schlüssel der obersten Ebene zuzugreifen. COALESCEwandelt die Ausgabe in ein leeres Array um, wenn JSON_KEYS NULL zurückgibt, und prüft dann, ob jeder Schlüssel in Kleinbuchstaben geschrieben ist.

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

Vorhandensein der Property erzwingen

Sie können eine Einschränkung erstellen, die prüft, ob eine Property für ein Label vorhanden ist.

Im folgenden Beispiel wird mit der Einschränkung geprüft, ob ein person-Knoten ein name-Attribut hat.

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

Eindeutigkeit von Properties erzwingen

Sie können eigenschaftsbasierte Einschränkungen erstellen, mit denen geprüft wird, ob die Eigenschaft eines Knotens oder einer Kante für alle Knoten oder Kanten mit demselben Label eindeutig ist. Verwenden Sie dazu einen UNIQUE INDEX für die generierten Spalten von Properties.

Im folgenden Beispiel wird mit dem eindeutigen Index geprüft, ob die kombinierten Eigenschaften name und country für jeden person-Knoten eindeutig sind.

  1. Fügen Sie eine generierte Spalte für PersonName hinzu.

    ALTER TABLE GraphNode
    ADD COLUMN person_name STRING(MAX)
    AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
    
  2. Fügen Sie eine generierte Spalte für PersonCountry hinzu.

    ALTER TABLE GraphNode
    ADD COLUMN person_country STRING(MAX)
    AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;
    
  3. Erstellen Sie einen eindeutigen NULL_FILTERED-Index für die Properties PersonName und PersonCountry.

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

Attributdatentypen erzwingen

Sie können einen Attributdatentyp mithilfe einer Datentypbeschränkung für einen Attributwert für ein Label erzwingen, wie im folgenden Beispiel gezeigt. In diesem Beispiel wird mit der Funktion JSON_TYPE geprüft, ob für die Property name des person-Labels der Typ STRING verwendet wird.

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

Definierte und dynamische Labels kombinieren

Mit Spanner können Knoten in Ihrer Property-Graph-Datei sowohl definierte Labels (im Schema definiert) als auch dynamische Labels (aus Daten abgeleitet) haben. Sie können Labels anpassen, um diese Flexibilität zu nutzen.

Im folgenden Schema wird die Erstellung der Tabelle GraphNode dargestellt:

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)
  );

Hier hat jeder Knoten, der über GraphNode erstellt wurde, das definierte Label Entity. Außerdem hat jeder Knoten ein dynamisches Label, das durch den Wert in der Label-Spalte bestimmt wird.

Sie können dann Abfragen schreiben, die Knoten basierend auf dem jeweiligen Labeltyp abgleichen. Mit der folgenden Abfrage werden beispielsweise Knoten mit dem definierten Label Entity gefunden:

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

Auch wenn in dieser Abfrage das definierte Label Entity verwendet wird, hat der übereinstimmende Knoten auch ein dynamisches Label, das auf seinen Daten basiert.

Schemabeispiele

Sie können die Schemabeispiele in diesem Abschnitt als Vorlagen verwenden, um Ihre eigenen Schemas zu erstellen. Zu den wichtigsten Schemakomponenten gehören:

Im folgenden Beispiel wird gezeigt, wie Sie Eingabetabellen und eine Property-Graphik erstellen:

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)
  );

Im folgenden Beispiel wird ein Index verwendet, um die Rückwärtstraversale von Kanten zu verbessern. Die STORING (properties)-Klausel enthält eine Kopie der Kanteneigenschaften, wodurch Abfragen beschleunigt werden, bei denen nach diesen Eigenschaften gefiltert wird. Sie können die STORING (properties)-Klausel weglassen, wenn sie für Ihre Abfragen nicht von Vorteil ist.

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

Im folgenden Beispiel wird ein Labelindex verwendet, um das Abgleichen von Knoten nach Labels zu beschleunigen.

CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);

Im folgenden Beispiel werden Einschränkungen hinzugefügt, die Kleinbuchstaben für Labels und Properties erzwingen. In den letzten beiden Beispielen wird die Funktion JSON_KEYS verwendet. Optional können Sie die Prüfung auf Kleinbuchstaben in der Anwendungslogik erzwingen.

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)));

Batch-Aktualisierungen dynamischer Properties mit DML optimieren

Wenn Sie dynamische Properties mit Funktionen wie JSON_SET und JSON_REMOVE ändern, werden Read-Modify-Write-Vorgänge ausgeführt. Sie können im Vergleich zur Aktualisierung von Properties vom Typ STRING oder INT64 zu höheren Kosten führen.

Wenn Ihre Arbeitslasten Batch-Änderungen an dynamischen Properties mit DML umfassen, können Sie mit den folgenden Empfehlungen die Leistung verbessern:

  • Aktualisieren Sie mehrere Zeilen in einer einzigen DML-Anweisung, anstatt Zeilen einzeln zu verarbeiten.

  • Wenn Sie einen Schlüsselbereich mit vielen Zeilen aktualisieren, gruppieren und sortieren Sie die betroffenen Zeilen nach ihren Primärschlüsseln. Wenn Sie nicht überlappende Bereiche mit jeder DML aktualisieren, wird die Sperrungskonfliktrate reduziert.

  • Verwenden Sie Abfrageparameter in DML-Anweisungen, anstatt sie hartzucodieren, um die Leistung zu verbessern.

Basierend auf diesen Vorschlägen wird im folgenden Beispiel die Property is_blocked für 100 Knoten in einer einzigen DML-Anweisung aktualisiert. Zu den Abfrageparametern gehören:

  1. @node_ids: Die Schlüssel von GraphNode-Zeilen, die in einem ARRAY-Parameter gespeichert sind. Wenn möglich, sollten sie in DMLs gruppiert und sortiert werden, um eine bessere Leistung zu erzielen.

  2. @is_blocked_values: Die entsprechenden Werte, die aktualisiert werden sollen, werden in einem ARRAY-Parameter gespeichert.

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)

Fehlerbehebung

In diesem Abschnitt wird beschrieben, wie Sie Probleme mit schemalosen Daten beheben.

Property wird im TO_JSON-Ergebnis mehrmals angezeigt

Problem

Im folgenden Knoten werden die Eigenschaften birthday und name als dynamische Eigenschaften in der Spalte JSON modelliert. Im JSON-Ergebnis des Graphenelements sind doppelte Eigenschaften von birthday und name zu sehen.

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

Dies gibt Ergebnisse wie die folgenden zurück:

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

Mögliche Ursache

Standardmäßig werden alle Spalten der Basistabelle als Eigenschaften definiert. Wenn Sie TO_JSON oder SAFE_TO_JSON verwenden, um Diagramme zurückzugeben, führt dies zu doppelten Properties. Das liegt daran, dass die Spalte JSON (properties) eine schemadefinierte Property ist, während die Schlüssel der ersten Ebene der JSON als dynamische Properties modelliert werden.

Empfohlene Lösung

Um dieses Verhalten zu vermeiden, schließen Sie die Spalte properties beim Definieren von Properties im Schema mit der Klausel PROPERTIES ALL COLUMNS EXCEPT aus, wie im folgenden Beispiel gezeigt:

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

Nach der Schemaänderung enthalten die zurückgegebenen Graphenelemente des Datentyps JSON keine Duplikate.

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

Diese Abfrage gibt Folgendes zurück:

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

Häufige Probleme bei der ordnungsgemäßen Umwandlung von Property-Werten

Eine häufige Lösung für die folgenden Probleme besteht darin, immer Property-Wert-Conversions zu verwenden, wenn eine Property in einem Abfrageausdruck verwendet wird.

Vergleich von Property-Werten ohne Conversion

Problem

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

Mögliche Ursache

Die Abfrage wandelt die Property-Werte nicht richtig um. Im Vergleich dazu wird das Attribut name nicht in den Typ STRING konvertiert:

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

Empfohlene Lösung

Verwenden Sie vor dem Vergleich eine Wertumwandlung, um dieses Problem zu beheben.

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

Dies gibt Ergebnisse wie die folgenden zurück:

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

Alternativ können Sie einen Property-Filter verwenden, um Gleichheitsvergleiche zu vereinfachen, bei denen die Wertumwandlung automatisch erfolgt. Der Werttyp („Alex“) muss genau mit dem STRING-Typ der Property in JSON übereinstimmen.

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

Dies gibt Ergebnisse wie die folgenden zurück:

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

RETURN DISTINCT-Property-Wert ohne Conversion verwenden

Problem

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

Mögliche Ursache

Im folgenden Beispiel wird order_number_str nicht konvertiert, bevor es in der RETURN DISTINCT-Anweisung verwendet wird:

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

Empfohlene Lösung

Verwenden Sie vor RETURN DISTINCT eine Wertumwandlung, um dieses Problem zu beheben.

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

Dies gibt Ergebnisse wie die folgenden zurück:

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

Property wird als Gruppierungsschlüssel ohne Conversion verwendet

Problem

Grouping by expressions of type JSON is not allowed.

Mögliche Ursache

Im folgenden Beispiel wird t.order_number_str nicht konvertiert, bevor es zum Gruppieren von JSON-Objekten verwendet wird:

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

Empfohlene Lösung

Um dieses Problem zu beheben, sollten Sie eine Wertumwandlung verwenden, bevor Sie die Property als Gruppierungsschlüssel verwenden.

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

Dies gibt Ergebnisse wie die folgenden zurück:

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

Attribut, das ohne Umwandlung als Sortierfeld verwendet wird

Problem

ORDER BY does not support expressions of type JSON

Mögliche Ursache

Im folgenden Beispiel wird t.amount nicht konvertiert, bevor es zum Sortieren von Ergebnissen verwendet wird:

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;

Empfohlene Lösung

Um dieses Problem zu beheben, führen Sie in der ORDER BY-Klausel eine Conversion für t.amount durch.

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;

Dies gibt Ergebnisse wie die folgenden zurück:

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

Nicht übereinstimmender Typ bei der Umwandlung

Problem

The provided JSON input is not an integer

Mögliche Ursache

Im folgenden Beispiel wird die Property order_number_str als JSON-STRING-Datentyp gespeichert. Wenn Sie versuchen, eine Umwandlung in INT64 vorzunehmen, wird ein Fehler zurückgegeben.

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

Empfohlene Lösung

Verwenden Sie zum Beheben dieses Problems den genauen Wertkonverter, der dem Werttyp entspricht.

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

Dies gibt Ergebnisse wie die folgenden zurück:

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

Alternativ können Sie einen flexiblen Konverter verwenden, wenn der Wert in den Zieltyp umgewandelt werden kann, wie im folgenden Beispiel gezeigt:

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

Dies gibt Ergebnisse wie die folgenden zurück:

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

Nächste Schritte