Introduction au langage de requête Cypher

Le langage de requête Cypher a été initialement développé spécifiquement pour le SGBD graphique Neo4j . L'objectif de Cypher est de fournir un langage de requête de base de données SQL lisible par l'homme pour les bases de données graphiques. Aujourd'hui, Cypher est supporté par plusieurs SGBD graphiques. OpenCypher a été créé pour standardiser Cypher.


Les bases de l'utilisation du SGBD Neo4j sont décrites dans l'article Notions de base de l'utilisation de Neo4j dans un navigateur .


Pour se familiariser avec Cypher, considérons un exemple d'arbre généalogique emprunté au manuel Prolog classique de I. Bratko. Cet exemple montre comment ajouter des nœuds et des liens à un graphique, comment leur attribuer des étiquettes et des attributs et comment poser des questions.


Arbre généalogique dans Neo4j, vue éditée


Alors, laissez-nous avoir un arbre généalogique montré dans l'image ci-dessous.


Arbre généalogique


Voyons comment former le graphe correspondant dans Cypher:


CREATE (pam:Person {name: "Pam"}), (tom:Person {name: "Tom"}), (kate:Person {name: "Kate"}), (mary:Person {name: "Mary"}), (bob:Person {name: "Bob"}), (liz:Person {name: "Liz"}), (dick:Person {name: "Dick"}), (ann:Person {name: "Ann"}), (pat:Person {name: "Pat"}), (jack:Person {name: "Jack"}), (jim:Person {name: "Jim"}), (joli:Person {name: "Joli"}), (pam)-[:PARENT]->(bob), (tom)-[:PARENT]->(bob), (tom)-[:PARENT]->(liz), (kate)-[:PARENT]->(liz), (mary)-[:PARENT]->(ann), (bob)-[:PARENT]->(ann), (bob)-[:PARENT]->(pat), (dick)-[:PARENT]->(jim), (ann)-[:PARENT]->(jim), (pat)-[:PARENT]->(joli), (jack)-[:PARENT]->(joli) 

Une demande CREATE pour ajouter des données à un SGBD graphique se compose de deux parties: ajouter des nœuds et ajouter des liens entre eux. Chaque nœud à ajouter se voit attribuer un nom dans le cadre de cette demande, qui est ensuite utilisé pour créer des liens. Les nœuds et les communications peuvent stocker des documents. Dans notre cas, les nœuds contiennent des documents avec les champs de nom et les liens de document ne contiennent pas. Les nœuds et les liens peuvent également être étiquetés. Dans notre cas, les nœuds se voient attribuer le label Personne et les liens sont PARENTS. L'étiquette dans les demandes est mise en évidence par deux points avant son nom.


Ainsi, Neo4j nous a dit que: Added 12 labels, created 12 nodes, set 12 properties, created 11 relationships, completed after 9 ms.


Voyons ce que nous avons:


 MATCH (p:Person) RETURN p 

Arbre généalogique à Neo4j


Personne ne nous interdit de modifier l'apparence du graphique résultant:


Arbre généalogique dans Neo4j, vue éditée


Que peut-on faire avec ça? Vous pouvez vérifier que, par exemple, Pam est
Parent de Bob:


 MATCH ans = (:Person {name: "Pam"})-[:PARENT]->(:Person {name: "Bob"}) RETURN ans 

Nous obtenons le sous-graphique correspondant:


Pam est le parent de Bob


Cependant, ce n'est pas exactement ce dont nous avons besoin. Modifiez la demande:


 MATCH ans = (:Person {name: "Pam"})-[:PARENT]->(:Person {name: "Bob"}) RETURN ans IS NOT NULL 

Maintenant, en réponse, nous devenons true . Et si nous demandons:


 MATCH ans = (:Person {name: "Pam"})-[:PARENT]->(:Person {name: "Liz"}) RETURN ans IS NOT NULL 

Nous n’obtiendrons rien ... Ici, vous devez ajouter le mot OPTIONAL , puis si
le résultat sera vide, puis false sera retourné:


 OPTIONAL MATCH ans = (:Person {name: "Pam"})-[:PARENT]->(:Person {name: "Liz"}) RETURN ans IS NOT NULL 

Maintenant, nous obtenons la réponse attendue false .


Ensuite, vous pouvez voir qui est le parent avec qui:


 MATCH (p1:Person)-[:PARENT]->(p2:Person) RETURN p1, p2 

Ouvrez l'onglet de résultat avec Text et voyez un tableau avec deux colonnes:


 ╒═══════════════╤═══════════════╕ │"p1" │"p2" │ ╞═══════════════╪═══════════════╡ │{"name":"Pam"} │{"name":"Bob"} │ ├───────────────┼───────────────┤ │{"name":"Tom"} │{"name":"Bob"} │ ├───────────────┼───────────────┤ │{"name":"Tom"} │{"name":"Liz"} │ ├───────────────┼───────────────┤ │{"name":"Kate"}│{"name":"Liz"} │ ├───────────────┼───────────────┤ │{"name":"Mary"}│{"name":"Ann"} │ ├───────────────┼───────────────┤ │{"name":"Bob"} │{"name":"Ann"} │ ├───────────────┼───────────────┤ │{"name":"Bob"} │{"name":"Pat"} │ ├───────────────┼───────────────┤ │{"name":"Dick"}│{"name":"Jim"} │ ├───────────────┼───────────────┤ │{"name":"Ann"} │{"name":"Jim"} │ ├───────────────┼───────────────┤ │{"name":"Pat"} │{"name":"Joli"}│ ├───────────────┼───────────────┤ │{"name":"Jack"}│{"name":"Joli"}│ └───────────────┴───────────────┘ 

Que pouvons-nous apprendre d'autre? Par exemple, qui est le parent d'un membre spécifique du genre, par exemple, pour Bob:


 MATCH (parent:Person)-[:PARENT]->(:Person {name: "Bob"}) RETURN parent.name 

 ╒═════════════╕ │"parent.name"│ ╞═════════════╡ │"Tom" │ ├─────────────┤ │"Pam" │ └─────────────┘ 

Ici, comme réponse, nous ne demandons pas le nœud entier, mais seulement son attribut spécifique.


Nous pouvons également découvrir qui sont les enfants de Bob:


 MATCH (:Person {name: "Bob"})-[:PARENT]->(child:Person) RETURN child.name 

 ╒════════════╕ │"child.name"│ ╞════════════╡ │"Ann" │ ├────────────┤ │"Pat" │ └────────────┘ 

On peut aussi demander qui a des enfants:


 MATCH (parent:Person)-[:PARENT]->(:Person) RETURN parent.name 

 ╒═════════════╕ │"parent.name"│ ╞═════════════╡ │"Pam" │ ├─────────────┤ │"Tom" │ ├─────────────┤ │"Tom" │ ├─────────────┤ │"Kate" │ ├─────────────┤ │"Mary" │ ├─────────────┤ │"Bob" │ ├─────────────┤ │"Bob" │ ├─────────────┤ │"Dick" │ ├─────────────┤ │"Ann" │ ├─────────────┤ │"Pat" │ ├─────────────┤ │"Jack" │ └─────────────┘ 

Hmm, Tom et Bob se sont rencontrés deux fois, corrigez-le:


 MATCH (parent:Person)-[:PARENT]->(:Person) RETURN DISTINCT parent.name 

Nous avons ajouté le mot DISTINCT au résultat de retour de la requête, ce qui signifie
similaire à celui de SQL.


 ╒═════════════╕ │"parent.name"│ ╞═════════════╡ │"Pam" │ ├─────────────┤ │"Tom" │ ├─────────────┤ │"Kate" │ ├─────────────┤ │"Mary" │ ├─────────────┤ │"Bob" │ ├─────────────┤ │"Dick" │ ├─────────────┤ │"Ann" │ ├─────────────┤ │"Pat" │ ├─────────────┤ │"Jack" │ └─────────────┘ 

Vous pouvez également remarquer que Neo4j nous renvoie les parents dans l'ordre dans lequel ils ont été entrés dans la demande CREATE .


Demandons maintenant qui est grand-père ou grand-mère:


 MATCH (grandparent:Person)-[:PARENT]->()-[:PARENT]->(:Person) RETURN DISTINCT grandparent.name 

Génial, c'est tout:


 ╒══════════════════╕ │"grandparent.name"│ ╞══════════════════╡ │"Tom" │ ├──────────────────┤ │"Pam" │ ├──────────────────┤ │"Bob" │ ├──────────────────┤ │"Mary" │ └──────────────────┘ 

Dans le modèle de requête, nous avons utilisé un nœud sans nom intermédiaire () et deux relations de type PARENT .


Nous découvrons maintenant qui est le père. Le père est un homme qui a un enfant. Ainsi, nous manquons de données sur qui est l'homme. Par conséquent, pour déterminer qui est une mère, vous devez savoir qui est une femme. Ajoutez les informations pertinentes à notre base de données. Pour ce faire, nous allons attribuer les étiquettes Male et Female aux nœuds existants.


 MATCH (p:Person) WHERE p.name IN ["Tom", "Dick", "Bob", "Jim", "Jack"] SET p:Male 

 MATCH (p:Person) WHERE p.name IN ["Pam", "Kate", "Mary", "Liz", "Ann", "Pat", "Joli"] SET p:Female 

Expliquons ce que nous avons fait ici: nous avons sélectionné tous les nœuds étiquetés Person , les avons vérifiés
la propriété de name fonction de la liste donnée spécifiée entre crochets et affectée respectivement aux nœuds correspondants l'étiquette Male ou Female .


Vérifier:


 MATCH (p:Person) WHERE p:Male RETURN p.name 

 ╒════════╕ │"p.name"│ ╞════════╡ │"Tom" │ ├────────┤ │"Bob" │ ├────────┤ │"Dick" │ ├────────┤ │"Jack" │ ├────────┤ │"Jim" │ └────────┘ 

 MATCH (p:Person) WHERE p:Female RETURN p.name 

 ╒════════╕ │"p.name"│ ╞════════╡ │"Pam" │ ├────────┤ │"Kate" │ ├────────┤ │"Mary" │ ├────────┤ │"Liz" │ ├────────┤ │"Ann" │ ├────────┤ │"Pat" │ ├────────┤ │"Joli" │ └────────┘ 

Nous avons demandé à tous les nœuds étiquetés Person , qui a également une étiquette de Male ou de Female , respectivement. Mais nous pourrions rendre nos demandes un peu différentes:


 MATCH (p:Person:Male) RETURN p.name MATCH (p:Person:Female) RETURN p.name 

Reprenons notre graphique:


Arbre généalogique avec des étiquettes Homme et Femme


Le navigateur Neo4j a peint les nœuds de deux couleurs différentes selon les marques de Male et
Femme


Ok, maintenant nous pouvons interroger tous les pères de la base de données:


 MATCH (p:Person:Male)-[:PARENT]->(:Person) RETURN DISTINCT p.name 

 ╒════════╕ │"p.name"│ ╞════════╡ │"Tom" │ ├────────┤ │"Bob" │ ├────────┤ │"Dick" │ ├────────┤ │"Jack" │ └────────┘ 

Et les mères:


 MATCH (p:Person:Female)-[:PARENT]->(:Person) RETURN DISTINCT p.name 

 ╒════════╕ │"p.name"│ ╞════════╡ │"Pam" │ ├────────┤ │"Kate" │ ├────────┤ │"Mary" │ ├────────┤ │"Ann" │ ├────────┤ │"Pat" │ └────────┘ 

Formulons maintenant une relation frère et sœur. X est frère de Y,
s'il est un homme, et pour X et Y il y a au moins un parent commun. De même pour
relation soeur.


Frère Attitude sur Cypher:


 MATCH (brother:Person:Male)<-[:PARENT]-()-[:PARENT]->(p:Person) RETURN brother.name, p.name 

 ╒══════════════╤════════╕ │"brother.name"│"p.name"│ ╞══════════════╪════════╡ │"Bob" │"Liz" │ └──────────────┴────────┘ 

Attitude des soeurs sur Cypher:


 MATCH (sister:Person:Female)<-[:PARENT]-()-[:PARENT]->(p:Person) RETURN sister.name, p.name 

 ╒═════════════╤════════╕ │"sister.name"│"p.name"│ ╞═════════════╪════════╡ │"Liz" │"Bob" │ ├─────────────┼────────┤ │"Ann" │"Pat" │ ├─────────────┼────────┤ │"Pat" │"Ann" │ └─────────────┴────────┘ 

Ainsi, nous pouvons découvrir qui est le parent et qui est aussi le grand-père ou la grand-mère. Mais qu'en est-il des ancêtres les plus éloignés? Avec des arrière-grands-pères, arrière-arrière-grands-pères, etc.? Nous n'écrirons pas de règle correspondante pour chacun de ces cas, et ce sera chaque fois plus problématique. En fait, tout est simple: X est un ancêtre pour Y s'il est un ancêtre pour un parent Y. Cypher fournit un modèle * qui vous permet d'exiger une séquence de relations de n'importe quelle longueur:


 MATCH (p:Person)-[*]->(s:Person) RETURN DISTINCT p.name, s.name 

Il y a vraiment un problème: ce seront les connexions. Ajoutez une référence au lien PARENT :


 MATCH (p:Person)-[:PARENT*]->(s:Person) RETURN DISTINCT p.name, s.name 

Afin de ne pas augmenter la longueur de l'article, on retrouve tous les ancêtres de Joli :


 MATCH (p:Person)-[:PARENT*]->(:Person {name: "Joli"}) RETURN DISTINCT p.name 

 ╒════════╕ │"p.name"│ ╞════════╡ │"Jack" │ ├────────┤ │"Pat" │ ├────────┤ │"Bob" │ ├────────┤ │"Pam" │ ├────────┤ │"Tom" │ └────────┘ 

Envisagez une règle plus complexe pour savoir qui est lié à qui.
Premièrement, les parents sont des ancêtres et des descendants, par exemple un fils et une mère, une grand-mère et un petit-fils. Deuxièmement, les parents sont des frères et sœurs, y compris des cousins, des cousins ​​au deuxième degré, etc., ce qui, en termes d'ancêtres, signifie qu'ils ont un ancêtre commun. Et troisièmement, les parents qui ont des descendants communs, par exemple le mari et la femme, sont considérés comme des parents.


Sur Cypher, vous devez utiliser UNION pour de nombreux modèles:


 MATCH (r1:Person)-[:PARENT*]-(r2:Person) RETURN DISTINCT r1.name, r2.name UNION MATCH (r1:Person)<-[:PARENT*]-(:Person)-[:PARENT*]->(r2:Person) RETURN DISTINCT r1.name, r2.name UNION MATCH (r1:Person)-[:PARENT*]->(:Person)<-[:PARENT*]-(r2:Person) RETURN DISTINCT r1.name, r2.name 

Ici, dans la première règle, des connexions sont utilisées, dont la direction n'a pas d'importance pour nous. Une telle connexion est indiquée sans flèche, juste un tiret - . Les deuxième et troisième règles sont écrites de manière évidente et familière.


Nous ne présenterons pas le résultat de la requête totale ici, nous dirons seulement que les paires de parents trouvées sont 132, ce qui est cohérent avec la valeur calculée comme le nombre de paires ordonnées à partir de 12. Nous pourrions également spécifier cette requête en remplaçant l'occurrence de la variable r1 ou r2 par (:Person {name: "Liz"}) par exemple, cependant, dans notre cas, cela n'a pas beaucoup de sens, car toutes les personnes dans notre base de données sont évidemment des parents.


Ceci conclut notre discussion sur l'identification des relations entre les personnes dans notre base de données.


Enfin, réfléchissez à la façon de supprimer les nœuds et les liens.


Pour supprimer toutes nos personnes, vous pouvez exécuter la demande:


 MATCH (p:Person) DELETE p 

Cependant, Neo4j nous dira que vous ne pouvez pas supprimer les nœuds qui ont des liens.
Par conséquent, nous supprimons d'abord les liens, puis répétons la suppression des nœuds:


 MATCH (p1:Person)-[r]->(p2:Person) DELETE r 

Ce que nous avons fait maintenant: a comparé deux personnes entre lesquelles il existe une connexion, a nommé cette connexion comme r , puis l'a supprimée.


Conclusion


L'article montre comment utiliser les capacités du langage de requête Cypher à l'aide d'un exemple simple de graphe social. En particulier, nous avons examiné comment ajouter des nœuds et des liens avec une seule requête, comment rechercher des données connexes, y compris avec des liens indirects, et comment attribuer des étiquettes aux nœuds. Vous trouverez plus d'informations sur Cypher sur les liens ci-dessous. Un bon point de départ est la "Neo4j Cypher Refcard".


Neo4j est loin d'être le seul SGBD graphique. Parmi les autres plus populaires, citons Cayley , Dgraph avec langage de requête GraphQL, ArangoDB multimodèle et OrientDB . Un intérêt particulier peut être Blazegraph avec le support de RDF et SPARQL.


Les références



Bibliographie


  • Robinson Jan, Weber Jim, Eifrem Emil. Bases de données graphiques. De nouvelles fonctionnalités
    pour travailler avec des données connexes / Per. de l'anglais - 2e éd. - M.: DMK-Press,
    2016 - 256 s.
  • Bratko I. Programmation en langage Prolog pour l'intelligence artificielle:
    trans. de l'anglais - M.: Mir, 1990 .-- 560 p.: Malade.

Postface


L'auteur de l'article ne connaît que deux sociétés (toutes deux de Saint-Pétersbourg) qui utilisent des SGBD graphiques pour leurs produits. Mais je voudrais savoir combien d'entreprises de lecteurs de cet article les utilisent dans leur développement. Par conséquent, je propose de participer à l'enquête. Écrivez également sur votre expérience dans les commentaires, il sera très intéressant de le savoir.

Source: https://habr.com/ru/post/fr482418/


All Articles