Aller au contenu

Premiers pas avec Cypher


Base de données exemple : Movie Graph

Noeuds et relations

C'est une BD graphe disponible avec Neo4j pour apprendre Cypher. Elle modélise les relations entre 2 noeuds :

  • Person : représentant une personne et peut être un acteur, réalisateur, produteur ou un spectateur
  • Movie : un film

Les relations possibles entre ces noeuds sont présentées dans le graphe ci-dessous.

Ce graphe comporte 133 noeuds de type Person et 38 Movie.

Propriétés

Les noeuds Movie possède les propriétés :

  • title : le titre du film qui est aussi son identifiant.
  • tagline : résumé du film.
  • released : date de parution du film.

Les noeuds Person possède les propriétés :

  • name : le nom de la personne qui est aussi son identifiant.
  • born : année de naissabce.

Les différentes relations possèdent aussi des propriétés :

Relation Propriétés
ACTED_IN propriété roles (liste des rôles d'un acteur dans un film)
DIRECTED ...
PRODUCED ...
WROTE ...
REVIEWED propriétés rating (évaluation) et summary (un commentaire)

Créer la base de données

Version Enterprise

La manipulation de plusieurs bases de données n'est supporté qu'avec la version Enterprise. Dans ce cas, avant de procéder aux opérations qui suivent commencer par :

  1. Créer une base de données "MovieGraph"
    CREATE DATABASE MovieGraph
    
  2. Basculer ver la nouvelle base de données
    :use MovieGraph
    

1⃣ Démarrer le guide de la base Movie Graph à partir du shell Neo4j

:guide movie-graph

guide

2⃣ Cliquer sur Next puissur le bloc de code pour l'insérer dans le shell

create movie-graph

Le code est disponible dans le fichier movie-graph.cypher

3⃣ Exécuter le code

3⃣ Vérifier le graphe complet

MATCH (n) RETURN n

graphe

Pattern Cypher

Le langage Cypher utilise des symboles spécifiques pour représenter les éléments du graphe dans ses requêtes.

Les neouds sont représentés par des parenthèses (). Pour désigner son type ou label on utilise : comme dans (:Person).

Les relation sont notées avec deux tirets reliant deux noeuds, par exemple : (:Person)--(:Movie).

La direction de la relation est indiquée avec > ou < , comme (:Person)→(:Movie) équivalent à (:Movie)←(:Person).

Le type de la relation peut être ajouté entre les tirets entre crochets , par exemple [:ACTED_IN].

Les propriétés des noeuds et des relations sont ajoutés dans un bloc JSON.

Des variables sont aussi ajoutées devant les labels comme (n:Person).

Pour des chemins de taille variable (n:Person)-[*]-(b:Person).

Pour des chemins de taille variable mais suivant un type de relation (n:Person)-[:ACTED*]-(b:Person).

Pour des chemins d'une taille spécifique (n:Person)-[*3..5]-(b:Person) (relation de niveau entre 3 et 5, la valeur 0 équivaut une absence de relation).

Exemple Cypher pattern

(m:Movie {title: 'Cloud Atlas'})<-[:ACTED_IN]-(p:Person)
Ce pattern représente tous les noeuds de type Person qui ont joué (ACTED_IN) dans le film, "Cloud Atlas".

Conventions

Voici quelques conventions à suivre avec Cypher :

  • Un label commence par majuscule : Person
  • Une relation est toute en majuscules : ACTED_IN
  • Une propriété est en minuscule : birthDate

Lecture de données

L'équivalent de SELECT dans Cypher est la commande MATCH.

Syntaxe MATCH
MATCH patter_cypher 
[WHERE <condition>] 
RETURN <expression1>, ... 
[SKIP <entier>]
[LIMIT <entier>]
[ORDER BY <champ> [DESC], ...]
Schéma de la BD

Visualiser le schéma de la base de données en cours permet de comprendre la structure du graphe de propriétés à manipuler avant de commencer à écrire des requêtes. Ce schéam peut être généré automatiquement avec la commande :

CALL db.schema.visualization

1⃣ Trouver tous les films

MATCH (n:Movie) RETURN n
Match1

2⃣ Trouver la date de sortie du film "The Matrix"

MATCH (n:Movie {title:'The Matrix'}) RETURN n.released as année

ou

MATCH (n:Movie) WHERE n.title='The Matrix' RETURN n.released as année

Match2

3⃣ Trouver les films réalisés par "Clint Eastwood".

MATCH (m:Movie)<-[:DIRECTED]-(:Person {name:'Clint Eastwood'}) RETURN m

Match3

4⃣ Trouver 3 films sortis avant 2000

MATCH (m:Movie) WHERE m.released<2000 RETURN m LIMIT 3
Match4

5⃣ Trouver les acteurs dont le prénom est "Michael"

MATCH (p:Person)-[:ACTED_IN]->()
WHERE toLower(p.name) STARTS WITH 'michael'
RETURN DISTINCT p.name
Match5

Cypher multi-ligne

Pour écrire du code Cypher sur plusieurs ligne, utiliser Shift+Enter pour retourner à la ligne. Dans ce cas, pour exécuter le bloc appuyer sur Ctrl+Enter.

6⃣ Quels sont les réalisateurs qui ont aussi joué un rôle dans le même film qu'ils ont réalisé.

MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE  exists( (p)-[:DIRECTED]->(m) )
RETURN p, m
Match6

7⃣ Quels sont les commentaires laissés par les utilisateurs lors de l'évaluation du film "The Da Vinci Code".

MATCH (p:Person)-[r:REVIEWED]->(m:Movie)
WHERE m.title='The Da Vinci Code' AND r.summary IS NOT NULL
RETURN r.summary

Match7

8⃣ Trouver les personnes qui suivies par les personnes qu'une personne suit (comme la relation amis de mes amis).

MATCH (p:Person)-[:FOLLOWS*2]->(q:Person) 
WHERE p <> q
RETURN p.name as Utilisateur,q.name as Second_niveau

Match8

Écriture de données

Insertion de noeuds

Pour créer des éléments de grpahe deux clauses sont utilisées par Cypher : CREATE et MERGE.

La différence entre ces clause est que MERGE requiert la saisie d'une valeur pour l'identifiant et évite d'avoir des doublons.

CREATE|MERGE <cypher_pattern>

1⃣ Créer un nouveau noeud Person

MERGE (:Person {name:'Tom Cruise'})

Aucun noeud créé, parce qu'il est déjà existant.

insert1

2⃣ Créer une relation entre noeuds existants

"Angela Scope" a évalué le film "Cloud Atlas" et a donné un rating=95

MATCH (n:Person {name:'Angela Scopee'})
MATCH (m:Movie {title:'Cloud Atlas'})
MERGE (n)-[:REVIEWED {rating:95}]->(m)
insert2

3⃣ Créer une structure

Ajouter un film 'Jurassic Park' sorti en 1993 et son réalisateur "Steven Spielberg".

MERGE (:Movie {title:'Jurassic Park', released:1993})<-[:DIRECTED]-(:Person {name:'Steven Spielberg'})

insert3

Mise à jour de noeuds

La clause MERGE sert aussi à mettre à jour les propriétés d'un noeud exitant. Il est possible de faire la mise à jour en associant MATCH et SET. Cette dernière permet de modifier la valeur d'un propriété existante ou définir une nouvelle propriété. Pour supprimer une propriété c'est REMOVE qui est utilisée.

1⃣ Modifier l'éavluation de "Angela Scope" du film "Cloud Atlas" en 92

MATCH (n:Person {name:'Angela Scopee'})-[r:REVIEWED {rating:95}]->(m:Movie {title:'Cloud Atlas'})
SET r.rating=92
RETURN n,m,r

update1

2⃣ Supprimer la propriété released du film 'Jurassic Park'.

MATCH (m:Movie) WHERE m.title='Jurassic Park'
    REMOVE m.released
update2

3⃣ Effectuer une mise à jour selon le comportenent de MERGE

// Trouver ou créer une personne
MERGE (p:Person {name: 'McKenna Grace'})

// Ajouter cette propriété `createdAt` si la personne est créée
ON CREATE SET p.createdAt = datetime()

// Ajouter lanpropriété `updatedAt` si la personne existe et n a pas é créée
ON MATCH SET p.updatedAt = datetime()

// Dans tous les cas modifier la propriété `born`
SET p.born = 2006

RETURN p

update3

4⃣ Ajouter un label à noeud : ajouter le label Actor pour tous les Person qui ont au moins joué dans un film.

MATCH (p:Person)
WHERE exists ((p)-[:DIRECTED]-())
SET p:Director

update4

Refactoring

L'ajout de label est une technique utilisée pour optimiser les requêtes par "Refactoring" du graphe. C'est à dire changement du modèle de graphe. Dans l'exemple précédent, nous avons ajouté un label pour toute personne qui a réalisé au moins un film. Nous pouvons donc utiliser ce nouveau label pour interroger directement le graphe avec le pattern (d:Director). Neo4j recommande un nombre maximal de 4 labels par noeuds.

Les figures suivantes permettent de comparer les plans d'exécution des 2 requêtes :

refactoring1

refactoring2

4⃣ Supprimer tous les labels Director

MATCH (p:Director)
REMOVE p:Director

update4

Suppression

C'est DELETE après avoir obtenu une référence vers l'élément à supprimer (avec MATCH par exemple).

1⃣ Supprimer le review de "Angela Scope" du film "Cloud Atlas".

MATCH (n:Person {name:'Angela Scopee'})-[r:REVIEWED]->(m:Movie {title:'Cloud Atlas'})
DELETE r

delete1

Suppression d'un noeud et ses relations

Quand un noeud possède des relation Neo4j empêche sa suppression. Pour supprimer automatiquement un noeud avec toutes ses relation, il faut utiliser DETACH DELETE

Agrégations

Les agrégations sont calculés dans la clause RETURN à travers des fonctions comme : COUNT, SUM, AVG, MIN, MAX, ...

1⃣ Calculer le nombre de films dans le graphe

MATCH (p:Person)
RETURN count(p) AS Total

Agg1

2⃣ Trouver le top 5 des acteurs et réalisateurs ayant le plus de collaborations

MATCH (a:Person)-[:ACTED_IN]->(m:Movie)<-[:DIRECTED]-(r:Person)
RETURN a.name as Acteur, r.name as Réalisateur, count(*) AS collaborations
ORDER BY collaborations DESC
LIMIT 5

Agg2

3⃣ Pour chaque film afficher les acteurs et leur nombre

MATCH (m:Movie)<-[:ACTED_IN]-(a:Person)
RETURN m.title AS Film, collect(a.name) AS casting, count(*) AS nb_acteurs
collect

La fonction collect permet de créer une liste des valeurs pour des structures de type parent-enfants ou 1-n. Dans cet exemple, un film (titre) a plusieurs acteurs. Collect permet de grouper les acteurs dans une liste pour ne pas répéter le film avec chaque acteur. C'est comme effectuer un GROUP BY sur le titre du film mais sans perdre le détail des enfants (acteurs).

Agg3

4⃣ Quels sont les réalisateurs de films sortis en 2008, 2009 et 2010

WITH [2008, 2009, 2010] AS années
UNWIND années AS an
MATCH (r:Person)-[:DIRECTED]->(m:Movie {released:an})
RETURN an, collect(r.name) as réalisateurs

Agg4

Index et contraintes

Les index permettent d'accélérer les requêtes et sont un outil indispensabe pour l'optimisation des requêtes.

Neo4j utilise 5 types d'index :

  • RANGE
  • LOOKUP
  • TEXT
  • FULLTEXT
  • POINT
CREATE [RANGE|LOOKUP|TEXT|FULLTEXT|POINT] INDEX <nom_index> FOR (n:label) ON (l.prop1, ...) 

1⃣ Créer un index sur la date de sortie d'un film

CREATE INDEX idx_film_released FOR (m:Movie) ON (m.released)
2⃣ Afficher les index

SHOW INDEXES

index

3⃣ Ajouter une contrainte d'unicité d'une propriété

CREATE CONSTRAINT [constraint_name] [IF NOT EXISTS]
FOR (n:LabelName)
REQUIRE (n.propertyName_1, , n.propertyName_n) IS UNIQUE
[OPTIONS "{" option: value[, ...] "}"]
Autres types de contraintes

D'autres contraintes sont disponibles mais uniquement dans la version Enterprise comme :

  • Existence de propriété de noeud
  • Existence de propriété de relation
  • Contrainte Node Key

Références du lanagage Cypher

Types

Dans Neo4j, les types suivants sont supportés :

  • Number
  • String
  • Boolean
  • The spatial type Point
  • Date, Time, LocalTime, DateTime, LocalDateTime et Duration
  • Lsit et Map

Opérateurs

Catégorie Opérateurs
Agrégation DISTINCT
Propriétés . accès, []: accès dynamique, =: affectation, +=: affectation de certains types de propriétés
Mathématiques +, -, *, /, %, ^
Comparaison <>, <, >, <=, >=, IS NULL, IS NOT NULL
Chaînes de caractères STARTS WITH, ENDS WITH, CONTAINS, =~ pour les expressions régulières, + concaténation
Booléens AND, OR, XOR, NOT
Dates et durées + et - entre dates et durées, * et / entre durées et nombres
Dictionnaires (Map) . accès statique par clé, [] accès dynamique par clé
Tableaux (List) + concaténation, IN test d'appartenance, [] accès à un élément

Fonctions

Famille Exemples
Prédicats all(), any(), exists(), isEmpty(), single()
String left(), right(), substring(), trim(), toUpper(), toLower(), split(), replace(), ...
List/Map keys(), labels(), nodes(), range(), relationships(), reverse(), size(), head(), last(), ...
Maths abs(), ceil(), floor(), round(), rand(), isNaN(), ...
Agrégats min(), max(), sum(), count(), avg(), stdev(), ...
Date/Time date(), datetime(), time(), datetime.fromepochmillis(), duration(), duration.between(), ....

Manipulation de bases de données

  • Lister les BD
    SHOW { DATABASE name | DATABASES | DEFAULT DATABASE | HOME DATABASE }
    [WHERE expression]
    
    SHOW { DATABASE name | DATABASES | DEFAULT DATABASE | HOME DATABASE }
    YIELD { * | field[, ...] } [ORDER BY field[, ...]] [SKIP n] [LIMIT n]
    [WHERE expression]
    [RETURN field[, ...] [ORDER BY field[, ...]] [SKIP n] [LIMIT n]]
    
  • Créer une BD
    CREATE DATABASE name [IF NOT EXISTS]
    [TOPOLOGY n PRIMAR{Y|IES} [m SECONDAR{Y|IES}]]
    [OPTIONS "{" option: value[, ...] "}"]
    [WAIT [n [SEC[OND[S]]]]|NOWAIT]
    
    CREATE OR REPLACE DATABASE name
    [TOPOLOGY n PRIMAR{Y|IES} [m SECONDAR{Y|IES}]]
    [OPTIONS "{" option: value[, ...] "}"]
    [WAIT [n [SEC[OND[S]]]]|NOWAIT]
    
    CREATE COMPOSITE DATABASE name [IF NOT EXISTS]
    [WAIT [n [SEC[OND[S]]]]|NOWAIT]
    
    CREATE OR REPLACE COMPOSITE DATABASE name
    [WAIT [n [SEC[OND[S]]]]|NOWAIT]
    
  • Modifier une BD
    ALTER DATABASE name [IF EXISTS]
    {
    SET ACCESS {READ ONLY | READ WRITE} |
    SET TOPOLOGY n PRIMAR{Y|IES} [m SECONDAR{Y|IES}]
    }
    
  • Démarrer une BD
    START DATABASE name [WAIT [n [SEC[OND[S]]]]|NOWAIT]
    
  • Arrêter une BD
    STOP DATABASE name [WAIT [n [SEC[OND[S]]]]|NOWAIT]
    
  • Supprimer une BD
    DROP [COMPOSITE] DATABASE name [IF EXISTS] [{DUMP|DESTROY} [DATA]] [WAIT [n [SEC[OND[S]]]]|NOWAIT]
    

Exercice 💻

Soit la base de données Northwind dont le schéma est le suivant :

northwind

  1. Créer un nouveau container Neo4j pour l'exercice
  2. Imorter les données en exécutant le code suivant :
    // tag::constraints[]
    CREATE CONSTRAINT FOR (o:Order) REQUIRE o.orderID IS UNIQUE;
    // end::constraints[]
    
    // tag::indexes[]
    CREATE INDEX FOR (m:Product) ON (m.productID);
    CREATE INDEX FOR (m:Product) ON (m.productName);
    CREATE INDEX FOR (m:Category) ON (m.categoryID);
    CREATE INDEX FOR (m:Employee) ON (m.employeeID);
    CREATE INDEX FOR (m:Supplier) ON (m.supplierID);
    CREATE INDEX FOR (m:Customer) ON (m.customerID);
    CREATE INDEX FOR (m:Customer) ON (m.customerName);
    // end::indexes[]
    
    // tag::nodes[]
    // Create customers
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/customers.csv" AS row
    CREATE (:Customer {companyName: row.CompanyName, customerID: row.CustomerID, fax: row.Fax, phone: row.Phone});
    
    // Create products
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/products.csv" AS row
    CREATE (:Product {productName: row.ProductName, productID: row.ProductID, unitPrice: toFloat(row.UnitPrice)});
    
    // Create suppliers
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/suppliers.csv" AS row
    CREATE (:Supplier {companyName: row.CompanyName, supplierID: row.SupplierID});
    
    // Create employees
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/employees.csv" AS row
    CREATE (:Employee {employeeID:row.EmployeeID,  firstName: row.FirstName, lastName: row.LastName, title: row.Title});
    
    // Create categories
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/categories.csv" AS row
    CREATE (:Category {categoryID: row.CategoryID, categoryName: row.CategoryName, description: row.Description});
    
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/orders.csv" AS row
    MERGE (order:Order {orderID: row.OrderID}) ON CREATE SET order.shipName =  row.ShipName;
    // end::nodes[]
    
    
    
    
    // tag::rels_orders[]
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/orders.csv" AS row
    MATCH (order:Order {orderID: row.OrderID})
    MATCH (product:Product {productID: row.ProductID})
    MERGE (order)-[pu:PRODUCT]->(product)
    ON CREATE SET pu.unitPrice = toFloat(row.UnitPrice), pu.quantity = toFloat(row.Quantity);
    
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/orders.csv" AS row
    MATCH (order:Order {orderID: row.OrderID})
    MATCH (employee:Employee {employeeID: row.EmployeeID})
    MERGE (employee)-[:SOLD]->(order);
    
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/orders.csv" AS row
    MATCH (order:Order {orderID: row.OrderID})
    MATCH (customer:Customer {customerID: row.CustomerID})
    MERGE (customer)-[:PURCHASED]->(order);
    // end::rels_orders[]
    
    // tag::rels_products[]
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/products.csv" AS row
    MATCH (product:Product {productID: row.ProductID})
    MATCH (supplier:Supplier {supplierID: row.SupplierID})
    MERGE (supplier)-[:SUPPLIES]->(product);
    
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/products.csv" AS row
    MATCH (product:Product {productID: row.ProductID})
    MATCH (category:Category {categoryID: row.CategoryID})
    MERGE (product)-[:PART_OF]->(category);
    // end::rels_products[]
    
    // tag::rels_employees[]
    
    LOAD CSV WITH HEADERS FROM "https://github.com/neo4j-documentation/developer-resources/raw/gh-pages/data/northwind/employees.csv" AS row
    MATCH (employee:Employee {employeeID: row.EmployeeID})
    MATCH (manager:Employee {employeeID: row.ReportsTo})
    MERGE (employee)-[:REPORTS_TO]->(manager);
    // end::rels_employees[]
    
  3. Répondre aux requêtes suivantes :
    1. Trouver les produits et leurs prix.
    2. Trouver les informations sur les produits 'Chocolade' & 'Pavlova'.
    3. Trouver les informations sur les produits dont le nom commence par "C” et le prix >50.
    4. Requête précédente en considérant "sales price" au lieu du prix du produit.
    5. Montant Total des achats par client et produit.
    6. Top 10 des employés selon les commandes vendues.
    7. Un employé est rattaché à combien de personnes de façon directe ou indirecte.
    8. À qui les personnes nommées “Robert” sont-ils rattachées.
    9. Qui n'a pas de personne à qui il est rattachée ?
    10. Trouver les fournisseurs (Suppliers), le nombre de catégories qu'ils fournissent et la liste de ces catégories.
    11. Le client qui a acheté le plus grand montant de la catégorie "beverages".
    12. Les 5 produits les plus populaires selon le nombre de commandes.