Gérer des données sans schéma avec Spanner Graph

Cette page explique comment gérer les données sans schéma dans Spanner Graph. Il détaille également les bonnes pratiques et les conseils de dépannage. Il est recommandé de connaître le schéma et les requêtes Spanner Graph.

La gestion des données sans schéma vous permet de créer une définition flexible d'un graphique, où les définitions de type de nœud et d'arête peuvent être ajoutées, mises à jour ou supprimées sans modification du schéma. Cette approche prend en charge le développement itératif et réduit les frais généraux de gestion des schémas, tout en préservant l'expérience familière des requêtes graphiques.

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

  • Vous gérez des graphiques qui changent fréquemment, par exemple en mettant à jour et en ajoutant des libellés et des propriétés d'éléments.
  • Votre graphique comporte de nombreux types de nœuds et d'arêtes, ce qui rend la création et la gestion des tables d'entrée difficiles.

Modéliser des données sans schéma

Spanner Graph vous permet de créer un graphique à partir de tables dont les lignes sont mappées sur des nœuds et des arêtes. Au lieu d'utiliser des tables distinctes pour chaque type d'élément, la modélisation des 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 le libellé 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 tables sont donnés à 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 opérations suivantes :

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

  • Stocke tous les nœuds dans une seule table GraphEdge, identifiée de manière unique par la combinaison de la source (id), de la destination (dest_id) et de son propre identifiant (edge_id). Un edge_id est inclus dans la clé primaire pour autoriser plusieurs nœuds d'une paire id à dest_id.

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

Créer un graphique de propriétés

Avec l'instruction CREATE PROPERTY GRAPH, les tables d'entrée de la section précédente sont mappées en tant que nœuds et arêtes. Vous devez utiliser les clauses suivantes pour définir les libellés et les propriétés des données sans schéma :

  • DYNAMIC LABEL : crée le libellé d'un nœud ou d'un bord à partir d'une colonne STRING de la table d'entrée.
  • DYNAMIC PROPERTIES : crée des 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)
  );

Libellé dynamique

La clause DYNAMIC LABEL désigne une colonne de type de données STRING pour stocker les valeurs de libellé.

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 de libellé a une valeur de owns, elle correspond à un bord Owns dans le graphique.

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

Propriétés dynamiques

La clause DYNAMIC PROPERTIES désigne une colonne de type de données JSON pour stocker les propriétés. Les clés JSON sont les noms de propriété et les valeurs JSON sont les valeurs de propriété.

Par exemple, lorsqu'une colonne properties d'une ligne GraphNode a la valeur JSON '{"name": "David", "age": 43}', elle correspond à un nœud avec les propriétés age et name, avec 43 et "David" comme valeurs de propriété.

Quand ne pas utiliser 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 n'ont pas besoin d'être mis à jour fréquemment.
  • 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 vous empêchent de les adopter.

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, il est plus efficace d'utiliser des propriétés définies par le schéma avec des types de données primitifs tels que STRING ou INT64 que d'utiliser des propriétés dynamiques avec le type JSON.

Pour savoir comment définir le schéma du graphique sans utiliser de libellés ni 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 dans l'aperçu des requêtes Spanner Graph et la documentation de référence sur GQL avec quelques modifications.

Faire correspondre des nœuds et des arêtes à l'aide de libellés

Vous pouvez faire correspondre des nœuds et des arêtes à l'aide de l'expression de libellé dans GQL.

La requête suivante correspond aux nœuds et aux arêtes connectés dont la colonne de libellé contient les valeurs account et transfers.

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

Propriétés d'accès

Les clés et valeurs de premier niveau du type de données JSON sont modélisées en tant que propriétés, telles que age et name dans l'exemple suivant.

JSON document Properties

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

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;

Elle renvoie des résultats semblables aux suivants :

JSON"Tom"

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

Les propriétés sont traitées comme des valeurs du type de données JSON. Dans certains cas, comme pour les comparaisons avec des types SQL, ils doivent d'abord être convertis en type SQL.

Dans l'exemple suivant, la requête effectue les conversions de type de données suivantes :

  • Convertit la propriété is_blocked en type booléen pour évaluer l'expression.
  • Convertit la propriété order_number_str en type chaîne et la compare à la valeur littérale "302290001255747".
  • Utilise la fonction LAX_INT64 pour convertir order_number_str en nombre entier en tant que type de valeur renvoyée.
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;

Elle renvoie des résultats semblables aux suivants :

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

Dans les clauses telles que GROUP BY et ORDER BY, vous devez également convertir le type de données JSON. L'exemple suivant convertit la propriété city en type chaîne, ce qui permet de l'utiliser pour le regroupement.

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

Conseils pour convertir les types de données JSON en types de données SQL :

  • Les convertisseurs stricts, tels que INT64, effectuent des vérifications rigoureuses des types et des valeurs. Cette méthode est recommandée lorsque le type de données JSON est connu et appliqué, par exemple en utilisant des contraintes de schéma pour appliquer le type de données de la propriété.
  • Les convertisseurs flexibles, tels que LAX_INT64, convertissent la valeur de manière sécurisée, lorsque cela est possible, et renvoient NULL lorsque la conversion n'est pas possible. Cette option est recommandée lorsqu'un contrôle rigoureux n'est pas nécessaire ou que les types sont difficiles à appliquer.

Pour en savoir plus sur la conversion des données, consultez les conseils de dépannage.

Filtrer par valeurs de propriété

Dans les filtres de propriété, les paramètres de filtre sont traités comme des valeurs de type de données JSON. Par exemple, dans la requête suivante, is_blocked est traité comme un boolean JSON et order_number_str comme un string JSON.

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

Elle renvoie des résultats semblables aux suivants :

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

Le paramètre de filtre doit correspondre au type et à la valeur de la propriété. Par exemple, lorsque le paramètre de filtre order_number_str est un entier, aucune correspondance n'est trouvée, car la propriété est un string JSON.

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

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

Les clés et valeurs JSON imbriquées ne sont pas modélisées en tant que propriétés. Dans l'exemple suivant, les clés JSON city, state et country ne sont pas modélisées en tant que propriétés, car elles sont imbriquées sous location. Toutefois, vous pouvez y accéder avec un opérateur d'accès aux champs JSON ou 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 STRING(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 aux nœuds et aux arêtes du graphique. Lorsque vous modifiez les données d'une table d'entrée, cela entraîne directement des mutations dans les données graphiques correspondantes. Pour en savoir plus sur la mutation des données graphiques, consultez Insérer, mettre à jour ou supprimer des données Spanner Graph.

Exemples

Cette section fournit des exemples de création, de mise à jour et de suppression de données graphiques.

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
}

Limites

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

Exigence concernant le tableau unique pour le libellé dynamique

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. Les pratiques suivantes ne sont pas autorisées :

  • Définition d'une table de nœuds avec un libellé dynamique à côté d'autres tables de nœuds.
  • Définition d'une table d'arêtes avec un libellé dynamique à côté d'autres tables d'arêtes.
  • Définir plusieurs tables de nœuds ou plusieurs tables d'arêtes qui utilisent chacune un libellé 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.

Pour que les valeurs de chaîne de libellé soient mises en correspondance, elles doivent être stockées 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 valeurs de chaîne des libellés doivent être stockées en minuscules, elles ne sont pas sensibles à la casse lorsqu'elles sont référencées dans une requête.

L'exemple suivant montre comment insérer des libellés 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

Les noms de propriétés doivent être stockés en minuscules. Nous vous recommandons d'appliquer cette règle dans le code de l'application ou à l'aide des contraintes du 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

  • Seules les clés de premier niveau du type de données JSON sont modélisées en tant que propriétés.
  • Les types de données de propriété doivent être conformes aux spécifications du type JSON Spanner.

Bonnes pratiques

Cette section décrit les bonnes pratiques pour modéliser les données sans schéma.

Définitions de clé primaire 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 vous avez plusieurs arêtes entre deux nœuds, vous devez introduire un identifiant unique pour l'arête. L'exemple de schéma utilise une colonne INT64 edge_id de logique d'application.

Lorsque vous créez le schéma des tables de nœuds et d'arêtes, vous pouvez éventuellement 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 de 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.

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, vous pouvez créer un index secondaire par rapport à une colonne de propriété générée, puis l'utiliser dans le schéma et les requêtes du graphique.

L'exemple suivant ajoute 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));

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

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

Le tableau GraphNode inclut désormais de nouvelles colonnes disponibles en tant que propriétés de nœud de graphique. Pour refléter cela dans la définition de votre 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é.

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êtes soit mis à jour avec les dernières statistiques de la base de données.

Vérifier les contraintes d'intégrité des données

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

Appliquer des valeurs de libellé valides

Nous vous recommandons d'utiliser NOT NULL dans la définition de la colonne "Libellé" pour éviter les valeurs de libellé indéfinies.

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

Appliquer les valeurs de libellé et les 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, nous vous recommandons d'effectuer l'une des actions 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 ajoute une contrainte de libellé de nœud à la table GraphNode pour s'assurer que le libellé est en minuscules.

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

L'exemple suivant ajoute 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 premier niveau. 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 de la propriété

Vous pouvez créer 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 l'unicité des propriétés

Vous pouvez créer des contraintes basées sur des 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 UNIQUE INDEX sur les colonnes générées des propriétés.

Dans l'exemple suivant, l'index unique vérifie que les propriétés name et country combinées sont uniques 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 les types de données de propriété

Vous pouvez appliquer 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 de votre graphique de propriétés d'avoir à la fois des libellés définis (dans le schéma) et des libellés dynamiques (dérivés des données). Vous pouvez personnaliser les libellés pour profiter de 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é".

Vous pouvez ensuite écrire des requêtes qui correspondent aux nœuds en fonction du type de libellé. Par exemple, la requête suivante recherche des nœuds à l'aide du libellé Entity défini :

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

Même si cette requête utilise le libellé défini Entity, n'oubliez pas que le nœud correspondant comporte également un libellé dynamique basé sur ses données.

Exemples de schéma

Vous pouvez utiliser les exemples de schémas de cette section comme modèles pour créer vos propres schémas. Voici les principaux composants du schéma :

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 de libellés pour accélérer la mise en correspondance des nœuds par libellés.

CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);

L'exemple suivant ajoute des contraintes qui imposent des libellés et des propriétés en minuscules. Les deux derniers exemples utilisent la fonction JSON_KEYS. Si vous le souhaitez, vous pouvez 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 par lot de 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/modification/écriture. Elles peuvent entraîner des coûts plus élevés que la mise à jour des propriétés de type STRING ou INT64.

Si vos charges de travail impliquent des mises à jour par lot des propriétés dynamiques à l'aide du langage LMD, tenez compte des recommandations suivantes pour améliorer les performances :

  • Mettez à jour plusieurs lignes dans une seule instruction LMD au lieu de les traiter individuellement.

  • Lorsque vous mettez à jour une large plage de clés, regroupez et triez les lignes concernées par leurs clés primaires. La mise à jour de plages non chevauchantes avec chaque LMD réduit la contention des verrous.

  • Utilisez des paramètres de requête dans les instructions LMD plutôt que de les coder en dur pour améliorer les performances.

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

  1. @node_ids : clés des lignes GraphNode, stockées dans un paramètre ARRAY. Si possible, regroupez-les et triez-les dans les DML pour obtenir de meilleures performances.

  2. @is_blocked_values : les 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 en double birthday et name apparaissent dans le résultat JSON de l'élément 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 propriétés. L'utilisation de TO_JSON ou 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 (c'est-à-dire 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 les résultats suivants :

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

Problèmes courants lorsque les valeurs de propriété ne sont pas correctement converties

Pour résoudre les problèmes suivants, il est courant d'utiliser systématiquement des conversions de valeurs de propriété lorsque vous utilisez une propriété dans une expression de requête.

Comparaison des valeurs de propriété sans conversion

Problème

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

Cause possible

La requête ne convertit pas correctement les valeurs de propriété. Par exemple, la propriété name n'est pas convertie en type STRING dans la comparaison :

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

Solution recommandée

Pour résoudre ce problème, utilisez une conversion de valeur avant la comparaison.

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

Elle renvoie des résultats semblables aux suivants :

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

Vous pouvez également utiliser un filtre de propriété pour simplifier les comparaisons d'égalité où la conversion de valeur est effectuée automatiquement. Notez que le type de la valeur ("Alex") doit correspondre exactement au type STRING de la propriété dans JSON.

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

Elle renvoie des résultats semblables aux suivants :

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

Utilisation de la valeur de la propriété RETURN DISTINCT sans conversion

Problème

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

Cause possible

Dans l'exemple suivant, order_number_str n'a pas été converti avant d'être utilisé dans l'instruction RETURN DISTINCT :

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

Solution recommandée

Pour résoudre ce problème, utilisez une conversion de valeur avant RETURN DISTINCT.

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

Elle renvoie des résultats semblables aux suivants :

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

Propriété utilisée comme clé de regroupement sans conversion

Problème

Grouping by expressions of type JSON is not allowed.

Cause possible

Dans l'exemple suivant, t.order_number_str n'est pas converti avant d'être utilisé pour regrouper des objets JSON :

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

Solution recommandée

Pour résoudre ce problème, utilisez une conversion de valeur avant d'utiliser la propriété comme clé de regroupement.

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

Elle renvoie des résultats semblables aux suivants :

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

Propriété utilisée comme clé de tri sans conversion

Problème

ORDER BY does not support expressions of type JSON

Cause possible

Dans l'exemple suivant, t.amount n'est pas converti avant d'être utilisé pour trier les résultats :

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;

Solution recommandée

Pour résoudre ce problème, effectuez une conversion sur t.amount dans la clause 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 DOUBLE(t.amount) DESC
LIMIT 1;

Elle renvoie des résultats semblables aux suivants :

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

Incompatibilité de type lors de la conversion

Problème

The provided JSON input is not an integer

Cause possible

Dans l'exemple suivant, la propriété order_number_str est stockée en tant que type de données JSON STRING. Si vous essayez d'effectuer une conversion vers INT64, une erreur s'affiche.

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

Solution recommandée

Pour résoudre ce problème, utilisez le convertisseur de valeur exacte qui correspond au type de valeur.

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

Elle renvoie des résultats semblables aux suivants :

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

Vous pouvez également utiliser un convertisseur flexible lorsque la valeur peut être convertie au type cible, comme indiqué dans l'exemple suivant :

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

Elle renvoie des résultats semblables aux suivants :

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

Étapes suivantes