Schemalose Daten mit Spanner Graph verwalten

Auf dieser Seite wird beschrieben, wie Sie schemalose Daten in Spanner Graph verwalten. Außerdem werden Best Practices und Tipps zur Fehlerbehebung beschrieben. Es wird empfohlen, sich mit dem Schema und den Abfragen von Spanner Graph vertraut zu machen.

Mit der schemalosen Datenverwaltung können Sie eine flexible Definition eines Graphen erstellen, in der die Definitionen für Knoten- und Kantentypen ohne Schemaänderungen hinzugefügt, aktualisiert oder gelöscht werden können. Dieser Ansatz unterstützt die iterative Entwicklung und reduziert den Aufwand für die Schemabearbeitung. Gleichzeitig bleibt die vertraute Erfahrung bei der Ausführung von Grafiabfragen erhalten.

Die schemalose Datenverwaltung ist in folgenden Szenarien besonders nützlich:

  • Sie verwalten Diagramme mit häufigen Änderungen, z. B. Aktualisierungen und Ergänzungen von Elementlabels und ‑attributen.
  • Ihr Diagramm enthält viele Knoten- und Kantentypen, was die Erstellung und Verwaltung von Eingabetabellen erschwert.

Daten ohne Schema modellieren

Mit Spanner Graph können Sie einen Graphen aus Tabellen erstellen, in denen Zeilen Knoten und Kanten zugeordnet werden. Anstatt separate Tabellen für jeden Elementtyp zu verwenden, wird beim schemalosen Datenmodellieren 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 schemaloser Daten erstellen, wie im folgenden Beispiel gezeigt. Die Tabellennamen dienen nur zur Veranschaulichung. Sie können Ihre eigenen Namen auswählen.

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.

  • Speichert alle Kanten in einer einzelnen Tabelle GraphEdge, die durch die Kombination aus Quelle (id), Ziel (dest_id) und eigener Kennung (edge_id) eindeutig identifiziert werden. Eine edge_id ist Teil des Primärschlüssels, um mehr als eine Kante von einem id- zu einem dest_id-Paar zu ermöglichen.

Sowohl die Knoten- als auch die Tabellen für Kanten 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 mit diesen Klauseln ein Diagramm 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 mit dem Datentyp STRING zum Speichern der Labelwerte festgelegt.

Wenn die Spalte label in einer GraphNode-Zeile beispielsweise 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 einer Owns-Kante im Diagramm zugeordnet.

Zuweisen eines `GraphNode`-Labels zu einem `GraphEdge`-Label

Dynamische Eigenschaften

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

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

Wann die schemalose Datenverwaltung nicht verwendet werden sollte

In den folgenden Szenarien sollten Sie die schemalose Datenverwaltung nicht verwenden:

  • Die Knoten- und Kantentypen für Ihre Grafdaten sind gut definiert oder ihre Labels und Eigenschaften müssen nicht häufig aktualisiert werden.
  • Ihre Daten sind bereits in Spanner gespeichert und Sie möchten lieber Diagramme aus vorhandenen Tabellen erstellen, anstatt neue, dedizierte Knoten- und Kantentabellen einzuführen.
  • Die Einschränkungen von schemalosen Daten verhindern die Einführung.

Wenn Ihr Arbeitslastprofil sehr empfindlich auf die Schreibgeschwindigkeit reagiert, insbesondere wenn Eigenschaften häufig aktualisiert werden, ist die Verwendung von schemadefinierten Eigenschaften mit primitiven Datentypen wie STRING oder INT64 effektiver als die Verwendung dynamischer Eigenschaften mit dem Typ JSON.

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

Schemafreie Diagrammdaten abfragen

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

Knoten und Kanten mithilfe von Labels abgleichen

Sie können Knoten und Kanten mit dem Label-Ausdruck in GQL abgleichen.

Die folgende Abfrage stimmt mit verbundenen Knoten und Kanten überein, deren Labelspalte die Werte account und transfers enthält.

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

Auf Eigenschaften zugreifen

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

Das folgende Beispiel zeigt, wie Sie über den Knoten Person auf die Property name zugreifen.

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

Dies gibt Ergebnisse wie die folgenden zurück:

JSON"Tom"

Attributdatentypen konvertieren

Attribute 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 durchgeführt:

  • Wandelt das Attribut is_blocked in einen booleschen Typ um, um den Ausdruck auszuwerten.
  • Konvertiert das Attribut order_number_str in einen Stringtyp und vergleicht es mit dem Literalwert "302290001255747".
  • Verwendet die Funktion LAX_INT64, um order_number_str sicher in einen Integer als Rückgabetyp zu konvertieren.
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 String-Typ konvertiert, 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:

  • Strenge Konverter wie INT64 führen strenge Typ- und Wertprüfungen durch. Dies wird empfohlen, wenn der JSON-Datentyp bekannt ist und erzwungen wird, z. B. durch die Verwendung von Schemabeschränkungen, um den Datentyp der Property zu erzwingen.
  • Flexible Konverter wie LAX_INT64 konvertieren den Wert nach Möglichkeit sicher und geben NULL zurück, wenn die Konvertierung nicht möglich ist. Dies wird empfohlen, wenn keine strenge Prüfung erforderlich ist oder Typen schwer zu erzwingen sind.

Weitere Informationen zur Datenkonvertierung

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 dem Property-Typ und -Wert entsprechen. Wenn der Filterparameter order_number_str beispielsweise eine Ganzzahl ist, wird keine Übereinstimmung gefunden, da die Property ein JSON-string ist.

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

Auf verschachtelte JSON-Attribute zugreifen

Verschachtelte JSON-Schlüssel und -Werte werden nicht als Attribute 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-Subscript-Operator 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",
}

Im folgenden Beispiel sehen Sie, wie Sie mit dem JSON-Feldzugriffsoperator auf verschachtelte Attribute 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 Eingabetabellendaten ändern, werden die entsprechenden Grafdaten direkt mutiert. Weitere Informationen zum Ändern von Graphdaten finden Sie unter Spanner Graph-Daten einfügen, aktualisieren oder löschen.

Beispiele

In diesem Abschnitt finden Sie Beispiele für das Erstellen, Aktualisieren und Löschen von Grafdaten.

Diagrammdaten einfügen

Im folgenden Beispiel wird ein person-Knoten eingefügt. Für Label- und Attributnamen muss Kleinschreibung verwendet 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 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 Gruppe von Eigenschaften 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 Eigenschaften unverändert.

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

Graphdaten löschen

Im folgenden Beispiel wird die Transfers-Kante für 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 bei der Verwendung der schemalosen Datenverwaltung aufgeführt.

Anforderung für dynamisches Label: nur eine Tabelle

Wenn in der Definition einer Knotentabelle ein dynamisches Label verwendet wird, kann es nur eine Knotentabelle geben. Diese Einschränkung gilt auch für die Edge-Tabelle. Folgendes ist nicht zulässig:

  • Eine Knotentabelle mit einem dynamischen Label neben anderen Knotentabellen definieren.
  • Eine Edge-Tabelle mit einem dynamischen Label neben anderen Edge-Tabellen definieren.
  • Mehrere Knotentabellen oder mehrere Kantentabellen definieren, die jeweils ein dynamisches Label verwenden.

Der folgende Code schlägt beispielsweise fehl, wenn versucht wird, mehrere Knoten 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 Schemabeschränkungen zu erzwingen.

Label-Stringwerte müssen zwar in Kleinbuchstaben gespeichert werden, bei der Bezugnahme in einer Abfrage wird aber 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 nicht beachtet wird, um GraphNode oder GraphEdge abzugleichen.

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

Eigenschaftsnamen müssen in Kleinbuchstaben geschrieben werden

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

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

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

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

Bei Attributnamen im Abfragetext wird nicht zwischen Groß- und Kleinschreibung unterschieden. Sie können beispielsweise entweder 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 schemaloser Daten beschrieben.

Definitionen von Primärschlüsseln für Knoten und Kanten

Der Schlüssel eines Knotens sollte für alle Knoten im Diagramm eindeutig sein. Beispiel: als INT64- oder Stringspalte UUID.

Wenn Sie mehrere Kanten zwischen zwei Knoten haben, müssen Sie eine eindeutige Kennung für die Kante einführen. Im Schema-Beispiel wird eine Spalte für die Anwendungslogik INT64 edge_id verwendet.

Wenn Sie das Schema für Knoten- und Kantentabellen erstellen, können Sie optional die Spalte label als Primärschlüsselspalte einfügen, sofern der Wert unveränderlich ist. In diesem Fall sollte der zusammengesetzte Schlüssel, der aus allen Schlüsselspalten besteht, für alle Knoten oder Kanten eindeutig sein. Diese Technik verbessert die Leistung von Abfragen, 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 häufig aufgerufene Property

Wenn Sie die Abfrageleistung für eine Eigenschaft verbessern möchten, die häufig in Filtern verwendet wird, können Sie einen sekundären Index für eine generierte Eigenschaftsspalte erstellen und ihn dann im Grafschema 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));

Anschließend wird ein NULL FILTERED INDEX für person_age erstellt und in die GraphNode-Tabelle 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 Eigenschaften von Grafikknoten verfügbar sind. Verwenden Sie die Anweisung CREATE OR REPLACE PROPERTY GRAPH, um dies in Ihrer Property Graph-Definition zu berücksichtigen. Dadurch wird die Definition neu kompiliert und die neue Spalte person_age als Attribut eingefügt.

Mit der folgenden Anweisung wird die Definition neu kompiliert und die neue Spalte person_age als Eigenschaft 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 den Befehl ANALYZE nach der Indexerstellung 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 Datenintegrität von Labels und Attributen zu erzwingen. In diesem Abschnitt finden Sie Empfehlungen für Check-Einschränkungen, die Sie mit schemalosen Daten verwenden können.

Gültige Labelwerte erzwingen

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

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

Labelwerte und Attributnamen in Kleinbuchstaben erzwingen

Da Label- und Attributnamen als Kleinbuchstaben gespeichert werden müssen, empfehlen wir Ihnen, eine der folgenden Maßnahmen zu ergreifen:

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

Im folgenden Beispiel wird der Tabelle GraphNode eine Knotenlabel-Einschränkung hinzugefügt, um sicherzustellen, dass das Label in Kleinbuchstaben ist.

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

Im folgenden Beispiel wird der Edge-Eigenschaftsname einer Check-Einschränkung hinzugefügt. Bei der Prüfung wird JSON_KEYS verwendet, um auf die Schlüssel der obersten Ebene zuzugreifen. Mit COALESCE wird die Ausgabe in ein leeres Array konvertiert, wenn JSON_KEYS NULL zurückgibt. Anschließend wird geprüft, 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 von Properties erzwingen

Sie können eine Einschränkung erstellen, mit der geprüft wird, ob für ein Label ein Attribut vorhanden ist.

Im folgenden Beispiel wird geprüft, ob ein person-Knoten das Attribut name hat.

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

Eindeutigkeit von Properties erzwingen

Sie können Property-basierte Einschränkungen erstellen, mit denen geprüft wird, ob die Property eines Knotens oder einer Kante für 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 Attribute name und country zusammen 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 NULL_FILTERED-Index für die Attribute PersonName und PersonCountry.

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

Datentypen von Attributen erzwingen

Sie können einen Attributdatentyp mit einer Datentypeinschrä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 name-Eigenschaft des Labels person der Typ STRING verwendet wird.

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

Kombination aus definierten und dynamischen Labels

In Spanner können Knoten in Ihrem Property Graph 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.

Sehen Sie sich das folgende Schema an, in dem die Erstellung der Tabelle GraphNode dargestellt wird:

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

Jeder Knoten, der aus GraphNode erstellt wird, hat das Label Entity mit der definierten Eigenschaft. Außerdem hat jeder Knoten ein dynamisches Label, das durch den Wert in der Spalte „Label“ bestimmt wird.

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

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 abgeglichene Knoten auch ein dynamisches Label, das auf seinen Daten basiert.

Schemabeispiele

Sie können die Schema-Beispiele in diesem Abschnitt als Vorlagen verwenden, um eigene Schemas zu erstellen. Zu den wichtigsten Schemakomponenten gehören:

Im folgenden Beispiel wird gezeigt, wie Eingabetabellen und ein Property Graph erstellt werden:

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 das Durchlaufen von Kanten in umgekehrter Richtung zu optimieren. Die STORING (properties)-Klausel enthält eine Kopie der Edge-Eigenschaften, was Abfragen beschleunigt, die nach diesen Eigenschaften filtern. Sie können die STORING (properties)-Klausel weglassen, wenn Ihre Anfragen nicht davon profitieren.

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

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

CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);

Im folgenden Beispiel werden Einschränkungen hinzugefügt, die erzwingen, dass Labels und Eigenschaften in Kleinbuchstaben angegeben werden. 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 Eigenschaften mit DML optimieren

Das Ändern dynamischer Eigenschaften mit Funktionen wie JSON_SET und JSON_REMOVE umfasst Read-Modify-Write-Vorgänge. Sie können zu höheren Kosten führen als das Aktualisieren von Attributen des Typs STRING oder INT64.

Wenn Ihre Arbeitslasten Batch-Aktualisierungen dynamischer Properties mit DML umfassen, sollten Sie die folgenden Empfehlungen berücksichtigen, um eine bessere Leistung zu erzielen:

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

  • Wenn Sie einen großen Schlüsselbereich aktualisieren, gruppieren und sortieren Sie die betroffenen Zeilen nach ihren Primärschlüsseln. Wenn Sie nicht überlappende Bereiche mit jeder DML-Anweisung aktualisieren, wird die Sperrenkonkurrenz reduziert.

  • Verwenden Sie Abfrageparameter in DML-Anweisungen anstelle von Hardcoding, um die Leistung zu verbessern.

Anhand dieser Vorschläge wird im folgenden Beispiel das Attribut is_blocked für 100 Knoten in einer einzelnen DML-Anweisung aktualisiert. Die Abfrageparameter umfassen Folgendes:

  1. @node_ids: Die Schlüssel der GraphNode-Zeilen, die in einem ARRAY-Parameter gespeichert sind. Falls zutreffend, wird durch das Gruppieren und Sortieren über DMLs hinweg eine bessere Leistung erzielt.

  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.

Die Property wird mehr als einmal im TO_JSON-Ergebnis angezeigt

Problem

Im folgenden Knoten werden die Attribute birthday und name als dynamische Attribute in der Spalte JSON dargestellt. Doppelte Attribute von birthday und name werden im JSON-Ergebnis des Grafikelements angezeigt.

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 Attribute definiert. Wenn Sie TO_JSON oder SAFE_TO_JSON verwenden, um Grafikelemente zurückzugeben, werden Eigenschaften dupliziert. Das liegt daran, dass die Spalte JSON (also properties) eine schemadefinierte Property ist, während die Schlüssel der ersten Ebene von JSON als dynamische Properties modelliert werden.

Empfohlene Lösung

Um dieses Verhalten zu vermeiden, verwenden Sie die PROPERTIES ALL COLUMNS EXCEPT-Klausel, um die Spalte properties auszuschließen, wenn Sie Attribute im Schema definieren, 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 Grafikelemente des Datentyps JSON keine Duplikate mehr.

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, wenn Attributwerte nicht richtig konvertiert werden

Ein häufiger Fix für die folgenden Probleme besteht darin, immer Property-Wert-Conversions zu verwenden, wenn eine Property in einem Abfrageausdruck verwendet wird.

Property-Werte ohne Conversion vergleichen

Problem

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

Mögliche Ursache

Die Abfrage konvertiert Attributwerte nicht richtig. Das Attribut name wird beispielsweise nicht in den Typ STRING konvertiert:

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

Empfohlene Lösung

Um dieses Problem zu beheben, verwenden Sie vor dem Vergleich eine Wertkonvertierung.

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 Wertkonvertierung automatisch erfolgt. Der Typ des Werts („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

Problem

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

Mögliche Ursache

Im folgenden Beispiel wurde 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 eine Wertkonvertierung vor RETURN DISTINCT, 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 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, verwenden Sie eine Wertkonvertierung, 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 als Sortierschlüssel ohne Conversion 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 eine Konvertierung für t.amount in der ORDER BY-Klausel 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    |
+--------------+------------+--------+

Typkonflikt bei der Konvertierung

Problem

The provided JSON input is not an integer

Mögliche Ursache

Im folgenden Beispiel wird das Attribut order_number_str als JSON-Datentyp STRING gespeichert. Wenn Sie versuchen, eine Konvertierung in INT64 durchzuführen, 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 den Konverter für genaue Werte, der dem Werttyp entspricht, um dieses Problem zu beheben.

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 Converter verwenden, wenn der Wert in den Zieltyp konvertiert 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