Gérer les données sans schéma

Cette page explique comment gérer les données sans schéma dans Spanner Graph. Il fournit également des bonnes pratiques et des conseils de dépannage. Nous vous recommandons de vous familiariser avec le schéma et les requêtes de Spanner Graph.

La gestion des données sans schéma vous permet de créer une définition de graphique flexible. Vous pouvez ajouter, mettre à jour ou supprimer des définitions de types de nœuds et d'arêtes sans modifier le schéma. Cette approche favorise le développement itératif et réduit les frais de gestion des schémas, tout en préservant l'expérience de requête de graphe familière.

La gestion des données sans schéma est utile dans les scénarios suivants :

  • Gérer des graphiques avec des modifications fréquentes, telles que des mises à jour et des ajouts d'étiquettes et de propriétés d'éléments.

  • Les graphiques comportant de nombreux types de nœuds et d'arêtes, ce qui rend la création et la gestion des tables d'entrée fastidieuses.

Pour en savoir plus sur les cas d'utilisation de la gestion des données sans schéma, consultez Considérations sur la gestion des données sans schéma.

Modéliser des données sans schéma

Spanner Graph vous permet de créer un graphique à partir de tables qui mappe les lignes aux nœuds et aux arêtes. Au lieu d'utiliser des tables distinctes pour chaque type d'élément, la modélisation de données sans schéma utilise généralement une seule table de nœuds et une seule table d'arêtes avec une colonne STRING pour l'étiquette et une colonne JSON pour les propriétés.

Créer des tables d'entrée

Vous pouvez créer une seule table GraphNode et une seule table GraphEdge pour stocker des données sans schéma, comme illustré dans l'exemple suivant. Les noms de table sont fournis à titre d'illustration. Vous pouvez choisir les vôtres.

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;

Cet exemple effectue les actions suivantes :

  • Stocke tous les nœuds dans une seule table, GraphNode, identifiée par un id unique.

  • Stocke toutes les arêtes dans une seule table, GraphEdge, identifiée par une combinaison unique de source (id), de destination (dest_id) et de son propre identifiant (edge_id). Un edge_id est inclus dans la clé primaire pour permettre plusieurs arêtes d'une paire id à dest_id.

Les tables de nœuds et d'arêtes ont chacune leurs propres colonnes label et properties. Ces colonnes sont respectivement de type STRING et JSON.

Pour en savoir plus sur les choix de clés pour la gestion des données sans schéma, consultez Définitions des clés primaires pour les nœuds et les arêtes.

Créer un graphique de propriétés

L'instruction CREATE PROPERTY GRAPH mappe les tables d'entrée de la section précédente en tant que nœuds et arêtes. Utilisez les clauses suivantes pour définir des libellés et des propriétés pour les données sans schéma :

  • DYNAMIC LABEL : crée l'étiquette d'un nœud ou d'un segment à partir d'une colonne STRING de la table d'entrée.
  • DYNAMIC PROPERTIES : crée les propriétés d'un nœud ou d'un bord à partir d'une colonne JSON de la table d'entrée.

L'exemple suivant montre comment créer un graphique à l'aide de ces clauses :

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

Définir des libellés dynamiques

La clause DYNAMIC LABEL désigne une colonne typée STRING pour stocker les valeurs d'étiquette.

Par exemple, dans une ligne GraphNode, si la colonne label a une valeur person, elle est mappée à un nœud Person dans le graphique. De même, dans une ligne GraphEdge, si la colonne d'étiquette a une valeur de owns, elle correspond à un bord Owns dans le graphique.

Mappage d'un libellé GraphNode à un libellé GraphEdge

Pour en savoir plus sur les limites liées à l'utilisation des libellés dynamiques, consultez Limites.

Définir des propriétés dynamiques

La clause DYNAMIC PROPERTIES désigne une colonne typée JSON pour stocker les propriétés. Les clés JSON représentent les noms des propriétés, et les valeurs JSON représentent les valeurs des propriétés.

Par exemple, lorsque la colonne properties d'une ligne GraphNode a la valeur JSON '{"name": "David", "age": 43}', Spanner la mappe à un nœud dont les propriétés age et name ont respectivement les valeurs 43 et "David".

Points à prendre en compte pour la gestion des données sans schéma

Vous ne souhaiterez peut-être pas utiliser la gestion des données sans schéma dans les scénarios suivants :

  • Les types de nœuds et d'arêtes de vos données graphiques sont bien définis, ou leurs libellés et propriétés ne nécessitent pas de mises à jour fréquentes.
  • Vos données sont déjà stockées dans Spanner et vous préférez créer des graphiques à partir de tables existantes plutôt que d'introduire de nouvelles tables de nœuds et d'arêtes dédiées.
  • Les limites des données sans schéma empêchent leur adoption.

De plus, si votre charge de travail est très sensible aux performances d'écriture, en particulier lorsque les propriétés sont fréquemment mises à jour, l'utilisation de propriétés définies par un schéma avec des types de données primitifs tels que STRING ou INT64 est plus efficace que l'utilisation de propriétés dynamiques avec le type JSON.

Pour en savoir plus sur la définition du schéma de graphique sans utiliser d'étiquettes et de propriétés de données dynamiques, consultez la présentation du schéma Spanner Graph.

Interroger des données de graphiques sans schéma

Vous pouvez interroger des données de graphe sans schéma à l'aide du langage GQL (Graph Query Language). Vous pouvez utiliser les exemples de requêtes de la présentation des requêtes Spanner Graph et de la documentation de référence sur GQL avec des modifications limitées.

Mettre en correspondance des nœuds et des arêtes à l'aide d'étiquettes

Vous pouvez mettre en correspondance des nœuds et des arêtes à l'aide de l'expression d'étiquette dans GQL.

La requête suivante met en correspondance les nœuds et les arêtes connectés qui ont les valeurs account et transfers dans leur colonne d'étiquette.

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

Propriétés d'accès

Les modèles Spanner utilisent les clés et les valeurs de premier niveau du type de données JSON comme propriétés, telles que age et name dans l'exemple suivant.

JSON document Properties

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

L'exemple suivant montre comment accéder à la propriété name à partir du nœud Person.

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

La requête renvoie des résultats semblables à ceux-ci :

"Tom"

Filtrer, comparer et trier par propriétés dynamiques

Vous pouvez utiliser des propriétés dynamiques directement dans les filtres de propriété de Spanner Graph et dans les clauses WHERE, GROUP BY, ORDER BY et RETURN DISTINCT. En effet, Spanner traite les propriétés dynamiques comme des valeurs du type de données JSON, qui est compatible avec la comparaison et le tri JSON.

Par exemple, la requête suivante filtre la propriété booléenne is_blocked et la propriété de chaîne order_number_str dans la clause WHERE :

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

Elle renvoie des résultats semblables aux suivants :

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

Vous pouvez exprimer les mêmes conditions d'égalité à l'aide de filtres de propriété de modèle d'élément :

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

Vous pouvez également utiliser des propriétés dynamiques directement dans les clauses GROUP BY, ORDER BY et RETURN DISTINCT :

  • Exemple GROUP BY :

    GRAPH FinGraph
    MATCH (person:Person {country: "South Korea"})
    RETURN person.city AS person_city, COUNT(*) AS cnt
    LIMIT 10;
    
  • Exemple ORDER BY :

    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;
    
  • Exemple RETURN DISTINCT :

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

Lorsque vous filtrez, comparez ou triez des propriétés dynamiques, les règles suivantes s'appliquent :

  • La valeur comparée doit correspondre à la fois au type JSON et à la valeur de la propriété. Par exemple, étant donné que order_number_str est stocké en tant que string JSON, la comparaison avec un littéral entier ne trouve aucune correspondance :

    GRAPH FinGraph
    MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str: 302290001255747}]->()
    RETURN t.order_number_str;
    
  • L'évaluation d'une propriété booléenne JSON directement en tant que condition WHERE autonome, telle que WHERE a.is_blocked, renvoie une erreur. Comparez plutôt la propriété avec un littéral booléen, tel que WHERE a.is_blocked = true, ou convertissez-la explicitement avec WHERE BOOL(a.is_blocked).

  • Pour connaître les règles et restrictions détaillées concernant la comparaison, le regroupement et le tri des valeurs JSON, consultez Comparaison et tri JSON et Contrainte de comparaison JSON.

Convertir les types de données des propriétés dynamiques

Convertissez les propriétés dynamiques en types de données SQL dans les scénarios suivants :

  • Convertir la valeur d'une propriété en un autre type de données pour la comparer ou l'afficher (par exemple, convertir la chaîne JSON "302290001255747" en entier INT64 ou renvoyer un type de données SQL au lieu de JSON).
  • Utiliser une valeur de propriété dans une fonction, un opérateur ou une expression SQL qui n'accepte pas les opérandes JSON, tels que les opérateurs arithmétiques (+, -), les fonctions de chaîne (LOWER, LIKE), les fonctions d'agrégation (SUM, AVG) ou IN UNNEST.
  • Définir une colonne générée sur une propriété pour un index secondaire ou un index unique, car les expressions JSON ne peuvent pas être indexées directement.

Dans l'exemple suivant, order_number_str est stocké en tant que string JSON, et la requête utilise la fonction LAX_INT64 pour le convertir en entier INT64 à des fins de comparaison et de renvoi :

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->()
WHERE a.is_blocked = true AND LAX_INT64(t.order_number_str) = 302290001255747
RETURN LAX_INT64(t.order_number_str) AS order_number_as_int64;

Elle renvoie des résultats semblables aux suivants :

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

Accéder aux propriétés JSON imbriquées

Spanner ne modélise pas les clés et valeurs JSON imbriquées en tant que propriétés. Dans l'exemple suivant, Spanner ne modélise pas les clés JSON city, state et country en tant que propriétés, car elles sont imbriquées sous location. Toutefois, vous pouvez y accéder à l'aide d'un opérateur d'accès aux champs JSON ou d'un opérateur d'indice JSON.

JSON document Properties

   {
     "name": "Tom",
     "age": 43,
     "location": {
       "city": "New York",
       "state": "NY",
       "country": "USA",
     }
   }
"name": "Tom"
"age": 34
"location": {
  "city": "New York",
  "state": "NY",
  "country": "USA",
}

L'exemple suivant montre comment accéder aux propriétés imbriquées avec l'opérateur d'accès au champ JSON.

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

Elle renvoie des résultats semblables aux suivants :

"New York"

Modifier des données sans schéma

Spanner Graph mappe les données des tables vers les nœuds et les arêtes du graphique. Lorsque vous modifiez les données d'une table d'entrée, cette modification entraîne directement des mutations dans les données du graphique correspondant. Pour en savoir plus sur la mutation des données graphiques, consultez Insérer, mettre à jour ou supprimer des données Spanner Graph.

Exemples de requêtes

Cette section fournit des exemples montrant comment créer, mettre à jour et supprimer des données de graphique.

Insérer des données de graphique

L'exemple suivant insère un nœud person. Les noms de libellés et de propriétés doivent être en minuscules.

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

Mettre à jour les données du graphique

L'exemple suivant met à jour un nœud Account et utilise la fonction JSON_SET pour définir sa propriété is_blocked.

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

L'exemple suivant met à jour un nœud person avec un nouvel ensemble de propriétés.

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

L'exemple suivant utilise la fonction JSON_REMOVE pour supprimer la propriété is_blocked d'un nœud Account. Après l'exécution, toutes les autres propriétés existantes restent inchangées.

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

Supprimer les données du graphique

L'exemple suivant supprime l'arête Transfers sur les nœuds Account qui ont été transférés vers des comptes bloqués.

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

Limitations connues

Cette section liste les limites de l'utilisation de la gestion des données sans schéma.

Exigence d'une table unique pour les étiquettes dynamiques

Vous ne pouvez avoir qu'une seule table de nœuds si un libellé dynamique est utilisé dans sa définition. Cette restriction s'applique également à la table Edge. Spanner n'autorise pas les opérations suivantes :

  • Définir une table de nœuds avec un libellé dynamique, ainsi que d'autres tables de nœuds.
  • Définir une table de périphérie avec une étiquette dynamique, ainsi que toutes les autres tables de périphérie.
  • Définir plusieurs tables de nœuds ou plusieurs tables d'arêtes qui utilisent chacune une étiquette dynamique.

Par exemple, le code suivant échoue lorsqu'il tente de créer plusieurs nœuds de graphique avec des libellés dynamiques.

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

Les noms des libellés doivent être en minuscules

Vous devez stocker les valeurs de chaîne des libellés en minuscules pour la mise en correspondance. Nous vous recommandons d'appliquer cette règle dans le code de l'application ou à l'aide de contraintes de schéma.

Bien que les valeurs de chaîne des libellés doivent être stockées en minuscules, elles ne sont pas sensibles à la casse lorsque vous les référencez dans une requête.

L'exemple suivant montre comment insérer des libellés dans des valeurs en minuscules :

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

Vous pouvez utiliser des libellés non sensibles à la casse pour faire correspondre GraphNode ou GraphEdge.

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

Les noms de propriétés doivent être en minuscules

Vous devez stocker les noms de propriétés en minuscules. Nous vous recommandons d'appliquer cette règle dans le code de l'application ou à l'aide de contraintes de schéma.

Bien que les noms de propriétés doivent être stockés en minuscules, ils ne sont pas sensibles à la casse lorsque vous les référencez dans votre requête.

L'exemple suivant insère les propriétés name et age en minuscules.

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

Dans le texte de la requête, les noms de propriétés ne sont pas sensibles à la casse. Par exemple, vous pouvez utiliser Age ou age pour accéder à la propriété.

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

Autres limites

  • Les modèles Spanner n'utilisent que les clés de premier niveau du type de données JSON comme propriétés.
  • Les types de données des propriétés doivent être conformes aux spécifications du type JSON de Spanner.

Bonnes pratiques pour les données sans schéma

Cette section décrit les bonnes pratiques qui vous aideront à modéliser des données sans schéma.

Définir des clés primaires pour les nœuds et les arêtes

La clé d'un nœud doit être unique pour tous les nœuds du graphique. Par exemple, en tant que colonne INT64 ou chaîne UUID.

Si plusieurs arêtes existent entre deux nœuds, introduisez un identifiant unique pour l'arête. L'exemple de schéma utilise une colonne de logique d'application INT64 edge_id.

Lorsque vous créez le schéma pour les tables de nœuds et d'arêtes, vous pouvez inclure la colonne label en tant que colonne de clé primaire si la valeur est immuable. Dans ce cas, la clé composite formée par toutes les colonnes clés doit être unique pour tous les nœuds ou toutes les arêtes. Cette technique améliore les performances des requêtes qui ne sont filtrées que par libellé.

Pour en savoir plus sur le choix d'une clé primaire, consultez Choisir une clé primaire.

Créer un index secondaire pour une propriété fréquemment consultée

Pour améliorer les performances des requêtes pour une propriété fréquemment utilisée dans les filtres, créez un index secondaire sur une colonne de propriété générée. Ensuite, utilisez-le dans un schéma et des requêtes de graphique.

L'exemple suivant montre comment ajouter une colonne age générée à la table GraphNode pour un nœud person. La valeur est NULL pour les nœuds sans libellé person.

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

L'instruction LDD suivante crée ensuite un NULL FILTERED INDEX pour person_age et l'entrelace dans la table GraphNode pour un accès local.

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

La table GraphNode inclut de nouvelles colonnes disponibles en tant que propriétés de nœud de graphique. Pour refléter cela dans la définition du graphique de propriétés, utilisez l'instruction CREATE OR REPLACE PROPERTY GRAPH. Cela recompile la définition et inclut la nouvelle colonne person_age en tant que propriété.

Pour en savoir plus, consultez Mettre à jour des définitions de nœuds ou d'arêtes existantes.

L'instruction suivante recompile la définition et inclut la nouvelle colonne person_age en tant que propriété.

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

L'exemple suivant exécute une requête avec la propriété indexée.

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

Vous pouvez également exécuter la commande ANALYZE après la création de l'index afin que l'optimiseur de requête soit mis à jour avec les dernières statistiques de la base de données.

Utiliser des contraintes de vérification pour l'intégrité des données

Spanner accepte les objets de schéma tels que les contraintes de vérification pour appliquer l'intégrité des données d'étiquette et de propriété. Cette section présente des recommandations concernant les contraintes de vérification que vous pouvez utiliser avec des données sans schéma.

Appliquer des valeurs de libellé

Nous vous recommandons d'utiliser NOT NULL dans la définition de la colonne d'étiquette pour éviter les valeurs d'étiquette non définies.

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

Appliquer des valeurs de libellé et des noms de propriété en minuscules

Étant donné que les noms de libellés et de propriétés doivent être stockés en minuscules, procédez de l'une des manières suivantes :

Au moment de la requête, le nom du libellé et de la propriété ne sont pas sensibles à la casse.

L'exemple suivant montre comment ajouter une contrainte d'étiquette de nœud à la table GraphNode pour s'assurer que l'étiquette est en minuscules.

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

L'exemple suivant montre comment ajouter une contrainte de vérification au nom de la propriété d'arête. La vérification utilise JSON_KEYS pour accéder aux clés de niveau supérieur. COALESCE convertit la sortie en tableau vide si JSON_KEYS renvoie NULL, puis vérifie que chaque clé est en minuscules.

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

Appliquer l'existence des propriétés

Créez une contrainte qui vérifie si une propriété existe pour un libellé.

Dans l'exemple suivant, la contrainte vérifie si un nœud person possède une propriété name.

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

Appliquer des propriétés uniques

Créez des contraintes basées sur les propriétés qui vérifient si la propriété d'un nœud ou d'un bord est unique parmi les nœuds ou les bords portant le même libellé. Pour ce faire, utilisez un INDEX UNIQUE sur les colonnes générées des propriétés.

Dans l'exemple suivant, l'index unique vérifie que la combinaison des propriétés name et country est unique pour tout nœud person.

  1. Ajoutez une colonne générée pour PersonName.

    ALTER TABLE GraphNode
    ADD COLUMN person_name STRING(MAX)
    AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
    
  2. Ajoutez une colonne générée pour PersonCountry.

    ALTER TABLE GraphNode
    ADD COLUMN person_country STRING(MAX)
    AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;
    
  3. Créez un index unique NULL_FILTERED pour les propriétés PersonName et PersonCountry.

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

Appliquer le type de données de la propriété

Appliquez un type de données de propriété à l'aide d'une contrainte de type de données sur une valeur de propriété pour un libellé, comme illustré dans l'exemple suivant. Cet exemple utilise la fonction JSON_TYPE pour vérifier que la propriété name du libellé person utilise le type STRING.

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

Combiner des libellés définis et dynamiques

Spanner permet aux nœuds du graphe de propriétés d'avoir à la fois des étiquettes définies (définies dans le schéma) et des étiquettes dynamiques (dérivées des données). Personnalisez les libellés pour utiliser cette flexibilité.

Prenons l'exemple du schéma suivant, qui montre la création de la table GraphNode :

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      LABEL Entity -- Defined label
      DYNAMIC LABEL (label) -- Dynamic label from data column 'label'
      DYNAMIC PROPERTIES (properties)
  );

Ici, chaque nœud créé à partir de GraphNode possède le libellé defined Entity. De plus, chaque nœud possède un libellé dynamique déterminé par la valeur de sa colonne "Libellé".

Ensuite, écrivez des requêtes qui correspondent aux nœuds en fonction de l'un ou l'autre type d'étiquette. Par exemple, la requête suivante recherche les nœuds à l'aide de l'étiquette Entity définie :

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

Même si cette requête utilise l'étiquette définie Entity, n'oubliez pas que le nœud correspondant comporte également une étiquette dynamique basée sur ses données.

Exemples de schéma

Utilisez les exemples de schémas de cette section comme modèles pour créer vos propres schémas. Les principaux composants du schéma sont les suivants :

  • Créer des tables d'entrée de graphiques
  • Création d'un graphique de propriété
  • Facultatif : index d'inversion de la traversée des arêtes pour améliorer les performances de traversée inverse
  • Facultatif : index des libellés pour améliorer les performances des requêtes par libellé
  • Facultatif : contraintes de schéma pour appliquer des libellés en minuscules et des noms de propriétés

L'exemple suivant montre comment créer des tables d'entrée et un graphique de propriétés :

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

L'exemple suivant utilise un index pour améliorer la traversée des arêtes inversées. La clause STORING (properties) inclut une copie des propriétés des arêtes, ce qui accélère les requêtes qui filtrent ces propriétés. Vous pouvez omettre la clause STORING (properties) si vos requêtes n'en bénéficient pas.

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

L'exemple suivant utilise un index d'étiquettes pour accélérer la mise en correspondance des nœuds par étiquette.

CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);

L'exemple suivant ajoute des contraintes qui appliquent des libellés et des propriétés en minuscules. Les deux derniers exemples utilisent la fonction JSON_KEYS. Vous pouvez éventuellement appliquer la vérification des minuscules dans la logique de l'application.

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

Optimiser les mises à jour groupées des propriétés dynamiques avec le langage LMD

La modification des propriétés dynamiques à l'aide de fonctions telles que JSON_SET et JSON_REMOVE implique des opérations de lecture, de modification et d'écriture. Cela peut entraîner un coût plus élevé que la mise à jour des propriétés de type STRING ou INT64.

Si les charges de travail impliquent des mises à jour groupées de propriétés dynamiques à l'aide du langage LMD, suivez les recommandations suivantes pour améliorer les performances :

  • Mettez à jour plusieurs lignes dans une seule instruction LMD plutôt que de traiter les lignes individuellement.

  • Lorsque vous mettez à jour une large plage de clés, regroupez et triez les lignes concernées par clés primaires. La mise à jour de plages qui ne se chevauchent pas avec chaque LMD réduit les conflits de verrouillage.

  • Utilisez des paramètres de requête dans les instructions LMD au lieu de les coder en dur pour améliorer les performances.

En fonction de ces suggestions, l'exemple suivant montre comment mettre à jour la propriété is_blocked pour 100 nœuds dans une seule instruction LMD. Les paramètres de requête incluent les éléments suivants :

  1. @node_ids : clés des lignes GraphNode, stockées dans un paramètre ARRAY. Le cas échéant, les regrouper et les trier entre les LMD permet d'obtenir de meilleures performances.

  2. @is_blocked_values : valeurs correspondantes à mettre à jour, stockées dans un paramètre ARRAY.

UPDATE GraphNode
SET properties = JSON_SET(
  properties,
  '$.is_blocked',
  CASE id
    WHEN @node_ids[OFFSET(0)] THEN @is_blocked_values[OFFSET(0)]
    WHEN @node_ids[OFFSET(1)] THEN @is_blocked_values[OFFSET(1)]
    ...
    WHEN @node_ids[OFFSET(99)] THEN @is_blocked_values[OFFSET(99)]
  END,
  create_if_missing => TRUE)
WHERE id IN UNNEST(@node_ids)

Résoudre les problèmes

Cette section explique comment résoudre les problèmes liés aux données sans schéma.

La propriété apparaît plusieurs fois dans le résultat TO_JSON

Problème

Le nœud suivant modélise les propriétés birthday et name en tant que propriétés dynamiques dans sa colonne JSON. Les propriétés birthday et name en double apparaissent dans le résultat JSON de l'élément de graphique.

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

Elle renvoie des résultats semblables aux suivants :

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

Cause possible

Par défaut, toutes les colonnes de la table de base sont définies comme des propriétés. L'utilisation de TO_JSON ou de SAFE_TO_JSON pour renvoyer des éléments de graphique entraîne des propriétés en double. Cela est dû au fait que la colonne JSON (properties) est une propriété définie par le schéma, tandis que les clés de premier niveau de JSON sont modélisées en tant que propriétés dynamiques.

Solution recommandée

Pour éviter ce comportement, utilisez la clause PROPERTIES ALL COLUMNS EXCEPT pour exclure la colonne properties lorsque vous définissez des propriétés dans le schéma, comme illustré dans l'exemple suivant :

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

Après la modification du schéma, les éléments de graphique renvoyés du type de données JSON ne comportent pas de doublons.

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

Cette requête renvoie le résultat suivant :

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

Étapes suivantes