Schemalose Daten 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. Wir empfehlen Ihnen, sich mit dem Schema und den Abfragen von Spanner Graph vertraut zu machen.

Mit der schemalosen Datenverwaltung können Sie eine flexible Graphdefinition erstellen. Sie können Knoten- und Kantentypdefinitionen hinzufügen, aktualisieren oder löschen, ohne dass sich das Schema ändert. Dieser Ansatz unterstützt die iterative Entwicklung und reduziert den Aufwand für die Schemabearbeitung, während die vertraute Graphabfrage beibehalten wird.

Die schemalose Datenverwaltung ist in folgenden Szenarien nützlich:

  • Verwaltung von Diagrammen mit häufigen Änderungen, z. B. Aktualisierungen und Ergänzungen von Elementlabels und ‑attributen.

  • Graphen mit vielen Knoten- und Kantentypen, was das Erstellen und Verwalten von Eingabetabellen erschwert.

Weitere Informationen dazu, wann die schemalose Datenverwaltung verwendet werden sollte, finden Sie unter Überlegungen zur schemalosen Datenverwaltung.

Daten ohne Schema modellieren

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

In diesem Beispiel werden die folgenden Aktionen ausgeführt:

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

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

Sowohl die Knoten- als auch die Kantentabelle haben eigene label- und properties-Spalten. Diese Spalten haben den Typ STRING bzw. JSON.

Weitere Informationen zur Auswahl von Schlüsseln für die schemalose Datenverwaltung finden Sie unter Primärschlüsseldefinitionen für Knoten und Kanten.

Property-Graph erstellen

Mit der Anweisung CREATE PROPERTY GRAPH werden die Eingabetabellen im vorherigen Abschnitt als Knoten und Kanten zugeordnet. Verwenden Sie die folgenden Klauseln, um Labels und Attribute 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 Attribute 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)
  );

Dynamische Labels definieren

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.

GraphNode-Label einem GraphEdge-Label zuordnen

Weitere Informationen zu Einschränkungen bei der Verwendung dynamischer Labels finden Sie unter Einschränkungen.

Dynamische Eigenschaften definieren

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

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

Hinweise zur schemalosen Datenverwaltung

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

  • Die Knoten- und Kantentypen für Ihre Graphdaten 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 Arbeitslast besonders 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 mithilfe des Label-Ausdrucks 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

In Spanner werden die Schlüssel und Werte der obersten Ebene des Datentyps JSON 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;

Die Abfrage gibt Ergebnisse wie die folgenden zurück:

JSON"Tom"

Attributdatentypen konvertieren

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

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 eine Ganzzahl 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 Stringtyp konvertiert, sodass Sie sie für die Gruppierung verwenden können.

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. Verwenden Sie strenge Konverter, wenn der JSON-Datentyp bekannt ist und erzwungen wird, z. B. durch Verwendung von Schemabeschränkungen, um den Datentyp des Attributs 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. Verwenden Sie flexible Konverter, wenn keine strenge Prüfung erforderlich ist oder Typen schwer zu erzwingen sind.

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

Nach Attributwerten filtern

In Attributfiltern behandelt Spanner die Filterparameter als Werte des Datentyps JSON. In der folgenden Abfrage behandelt Spanneris_blocked als JSON-boolean und order_number_str als JSON-string.

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, findet Spanner keine Übereinstimmung, 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

In Spanner werden verschachtelte JSON-Schlüssel und -Werte nicht als Attribute modelliert. Im folgenden Beispiel werden die JSON-Schlüssel city, state und country nicht als Attribute 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 Daten in der Eingabetabelle ä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.

Beispielabfragen

In diesem Abschnitt finden Sie Beispiele, die zeigen, wie Sie Grafdaten erstellen, aktualisieren und löschen.

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 das Attribut 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 auf gesperrte 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
}

Bekannte Einschränkungen

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

Anforderung für dynamische Labels: 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. In Spanner ist Folgendes 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.
  • Sie definieren mehrere Knotentabellen oder mehrere Kantentabellen, 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-String-Werte müssen für den Abgleich in Kleinbuchstaben gespeichert werden. Wir empfehlen, diese Regel entweder im Anwendungscode oder mithilfe von Schemabeschränkungen zu erzwingen.

Label-Stringwerte müssen zwar in Kleinbuchstaben gespeichert werden, bei einem Verweis 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

Sie müssen Attributnamen in Kleinbuchstaben speichern. 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;

Zusätzliche Einschränkungen

Best Practices für schemalose Daten

In diesem Abschnitt werden Best Practices beschrieben, mit denen Sie schemalose Daten modellieren können.

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

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

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

Wenn Sie das Schema für Knoten- und Tabellen erstellen, können Sie optional die Spalte label als Primärschlüsselspalte einfügen, wenn der Wert unveränderlich ist. In diesem Fall sollte der zusammengesetzte Schlüssel, der aus allen Schlüsselspalten gebildet wird, 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ären Index für eine häufig aufgerufene Eigenschaft erstellen

Wenn Sie die Abfrageleistung für eine Property verbessern möchten, die häufig in Filtern verwendet wird, erstellen Sie einen sekundären Index für eine generierte Property-Spalte. Anschließend können Sie es in einem Grafschema und in Abfragen verwenden.

Im folgenden Beispiel wird gezeigt, wie Sie der Tabelle GraphNode für einen person-Knoten eine generierte age-Spalte hinzufügen. Der Wert ist NULL für Knoten ohne das Label person.

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

Mit der folgenden DDL-Anweisung wird dann ein NULL FILTERED INDEX für person_age erstellt und für den lokalen Zugriff in die Tabelle GraphNode eingefügt.

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

Die Tabelle GraphNode enthält neue Spalten, die als Eigenschaften von Grafikknoten verfügbar sind. Um dies in der Definition des Property-Graphen zu berücksichtigen, verwenden Sie die CREATE OR REPLACE PROPERTY GRAPH-Anweisung. Dadurch wird die Definition neu kompiliert und die neue Spalte person_age als Attribut eingefügt.

Weitere Informationen finden Sie unter Vorhandene Knoten- oder Kantendefinitionen aktualisieren.

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 ANALYZE-Befehl nach dem Erstellen des Index ausführen, damit der Abfrageoptimierer mit den neuesten Datenbankstatistiken aktualisiert wird.

CHECK-Einschränkungen für die Datenintegrität verwenden

Spanner unterstützt Schemaobjekte wie CHECK-Einschrä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.

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

Kleinschreibung für Labelwerte und Attributnamen erzwingen

Da Label- und Attributnamen als Kleinbuchstaben gespeichert werden müssen, haben Sie folgende Möglichkeiten:

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

Im folgenden Beispiel wird gezeigt, wie Sie der Tabelle GraphNode eine Einschränkung für Knotenlabels hinzufügen, um sicherzustellen, dass das Label in Kleinbuchstaben ist.

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

Das folgende Beispiel zeigt, wie Sie der Edge-Eigenschaft „name“ eine Check-Einschränkung hinzufügen. 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 Attributen erzwingen

Erstellen Sie eine Einschränkung, mit der geprüft wird, ob für ein Label eine Eigenschaft 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));

Eindeutige Properties erzwingen

Erstellen Sie eigenschaftsbasierte Einschränkungen, mit denen geprüft wird, ob die Eigenschaft 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);
    

Datentyp des Attributs erzwingen

Sie können einen Attributdatentyp erzwingen, indem Sie eine Datentypeinschränkung für einen Attributwert für ein Label festlegen, wie im folgenden Beispiel gezeigt. In diesem Beispiel wird mit der Funktion JSON_TYPE geprüft, ob für das Attribut name des Labels person 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

In Spanner können Knoten im Attributdiagramm sowohl definierte Labels (im Schema definiert) als auch dynamische Labels (aus Daten abgeleitet) haben. Passen Sie die Labels an, 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 schreiben Sie Abfragen, die Knoten basierend auf dem 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

Verwenden Sie die Schema-Beispiele in diesem Abschnitt als Vorlagen, um Ihre eigenen 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 Kleinbuchstaben für Labels und Eigenschaften 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 Eigenschaften mit DML optimieren

Das Ändern dynamischer Eigenschaften mit Funktionen wie JSON_SET und JSON_REMOVE umfasst Read-Modify-Write-Vorgänge. Das kann zu höheren Kosten führen als bei der Aktualisierung von Eigenschaften des Typs STRING oder INT64.

Wenn Arbeitslasten Batch-Aktualisierungen dynamischer Eigenschaften mit DML umfassen, sollten Sie die folgenden Empfehlungen beachten, um eine bessere Leistung zu erzielen:

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

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

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

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

  1. @node_ids: Schlüssel von 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, gespeichert in einem ARRAY-Parameter.

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 mehrmals im TO_JSON-Ergebnis angezeigt

Problem

Im folgenden Knoten werden die Attribute birthday und name als dynamische Attribute in der Spalte JSON modelliert. Doppelte birthday- und name-Attribute 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 (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

Um die folgenden Probleme zu beheben, verwenden Sie immer Attributwertkonvertierungen, wenn Sie ein Attribut in einem Abfrageausdruck verwenden.

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, sollten Sie vor dem Vergleich eine Wertkonvertierung durchführen.

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