Introduction

Cette partie offre un aperçu de ce qu’est une base de données graphe et souligne ensuite quelques spécificités de Neo4j.

Points forts de Neo4j

Définie comme une base de données robuste, évolutive et très performante, Neo4j convient aussi bien pour le deploiement en grande entreprise que pour les projets plus légers en utilisant une partie du serveur complet.

Fonctionnalités:

  • true ACID transactions,

  • high availability,

  • scales to billions of nodes and relationships,

  • high speed querying through traversals,

  • declarative graph query language.

Un comportement ACID sain est le fondement même de la fiabilité. Neo4j renforce cela en s’assurant que toutes les opérations modifiant les données soient exécutées au sein d’une transaction, garantissant la cohérence des données. Cette robustesse s'étend depuis une simple instance de graphe embarquée vers des installations multi-serveurs à haute disponibilité. Pour plus de détails, reportez-vous à [transactions].

Un système de stockage en graphe fiable peut facilement être ajouté à n’importe quelle application. Un graphe s'étend en taille et complexité au fur et à mesure que l’application évolue, avec de faibles impacts sur la performance. Pour le commencement d’un nouveau projet comme pour l’ajout de fonctionnalités existantes, Neo4j est adapté et n’est limité que par le matériel physique.

Une seule instance serveur peut traiter un graphe contenant des milliards de noeuds et relations. Quand le taux de transfert est insuffisant, la base de données graphe peut être distribuée sur plusieurs serveurs dans une configuration haute-disponibilité. Lisez [ha] pour en apprendre plus.

Une base de données graphe brille quand elle stocke des données hautement connectées. Les requêtes sont exécutées à l’aide de traversées, qui peuvent procéder à plusieurs millions de traversées par seconde. Une traversée ressemble à une jointure dans les bases de données relationnelles.

Concepts d’une base de données graphe

Ce chapitre contient une introduction au modèle de données en graphe et le compare également à d’autres modèles de données utilisés pour la persistance de données.

Qu’est-ce qu’une base de données graphe?

Une base de données graphe stocke les données en graphe, structure de données la plus générique, capable de représenter avec élégance n’importe quel type de données d’une manière ultra accessible. Laissons nous guider le long de quelques graphes afin d’exprimer certains concepts. Nous "lirons" le graphe tout simplement en suivant ses flèches dans le diagramme afin de former des phrases.

Un graphe contient des Noeuds et des Relations

«Un graphe —enregistre les données en→ Noeuds —qui contiennent des→ Propriétés»

The simplest possible graph is a single Node, a record that has named values referred to as Properties. A Node could start with a single Property and grow to a few million Properties, though that can get a little awkward. At some point it makes sense to distribute the data into multiple nodes, organized with explicit Relationships.

graphdb-GVE.svg

Les Relations organisent le graphe

«Les Noeuds —sont organisés par des→ Relations —qui contiennent également des→ Propriétés»

Les Relations organisent les Noeuds en structures arbitraires, permettant à un Graphe de ressembler à une Liste, une Hiérarchie, une Carte ou une Entité composée — chacune de ces ressemblances pouvant être combinées en une structure beaucoup plus complexe et richement connectée.

Interroger un Graphe à l’aide d’une Traversée

«Une Traversée —navigue dans→ un Graphe; elle —identifie des→ Chemins —qui ordonnent les→ Noeuds»

uUne Traversée est la manière dont vous interrogez un Graphe, naviguant depuis les Noeuds de départ vers les Noeuds reliés en accordance avec un algorithme, répondant à des questions «Quelles chansons aiment mes amis mais que je ne possède pas?» ou «si cette génératrice d'énergie tombe en panne, quels services web sont affectés?»

graphdb-traversal.svg

Les indexs indentifient des Noeuds ou des Relations

«Un Index —relie des→ Properiétés —à des→ Noeuds ou Relations»

Parfois vous voulez trouver un Noeud ou une Relation spécifique en accordance avec une Propriété qu’elle contient. Plutôt que de traverser un Graphe entier, utilisez un Index afin de procéder à une recherche comme «trouver le Compte ayant comme nom d’utilisateur maître-des-graphes.»

graphdb-indexes.svg

Neo4j est une base de données Graphe

«Une base de données Graphe —gère un→ Graphe et —gère aussi les→ Indexs»

Neo4j est une base de données graphe open-source supportée commercialement. Elle a été conceptualisée et construite depuis le début afin d'être une base de données fiable, optimisée pour les structures en graphe en place et lieu de tables. En travaillant avec Neo4j, votre application profite de toutes les expressivités d’un graphe tout en vous garantissant la fiabilité que vous attendez d’une base de données.

graphdb-overview.svg

Comparaison des modèles de données

Une base de données Graph stocke les données structurées dans les Noeuds et Relatins d’un graphe. Comment cela est-il comparé à d’autres modèles de persistance? Du fait qu’un graphe est une structure générique, comparons ce à quoi d’autres modèles auraient l’air dans un graphe.

Une base de données graphe transforme un SGBDR

Renversez la pile d’enregistrements d’une base de données relationnelle et vous verrez un graphe. Là où un SGBDR est optimisé pour les données aggrégées, Neo4j est optimisé pour les données fortement connectées.

graphdb-compare-rdbms.svg
Figure 1. SGBDR
graphdb-compare-rdbms-g.svg
Figure 2. Base de données Graphe comme SGBDR

Une Base de données Graphe conçoit les Stockages Clé-Valeur

Un modèle Clé-Valeur est très bien pour la recherche de simples valeurs ou de listes. Quand ces valeurs sont elle-mêmes interconnectées, vous obtenez un graphe. Neo4j vous laisse concevoir une simple structure de données en structure données interconnectées et plus complexes.

graphdb-compare-kvstore.svg
Figure 3. Key-Value Store

K* représente une clé, V* une valeur. Notez que certaines clés pointent vers d’autres clés comme vers des valeurs.

graphdb-compare-kvstore-g.svg
Figure 4. Graph Database as Key-Value Store

Une Base de données Graphe conçoit les stockages Orientés Colonnes

La famille des stockages Orientés Colonnes (style BigTable) sont une évolution du modèle Clé-Valeur, utilisant des "familles" afin de grouper certaines lignes. Enregistrées dans un graphe, ces familles peuvent devenir hiérarchiques et les relations entre les données deviennent explicites.

Une Base de données Graphe navigue à travers un stockage orienté Documents

L’hiérarchie en conteneur d’une base de données orientée document conçoit facilement les données non schématisées qui peuvent être représentées en Arbre, qui est évidemment un graphe. Référez-vous à d’autres documents (ou éléments de documents) au sein de ce même arbre et vous obtenez une représentation plus expressive de ces mêmes données. Dans Neo4j, ces relations sont facilement naviguables.

graphdb-compare-docdb.svg
Figure 5. Document Store

D=Document, S=Sousdocument, V=Valeur, D2/S2 = Référence à un Sousdocument dans un (autre) Document.

graphdb-compare-docdb-g.svg
Figure 6. Graph Database as Document Store

La base de données Graphe Neo4j

Ce chapître entre plus en détails dans le comportement et le modèle de données de Neo4j.

Noeuds

Les unités fondamentales composant un graphe sont les noeuds et relations. Dans Neo4j, aussi bien les noeuds que les relations peuvent contenir des propriétés.

Les noeuds sont souvent utilisés pour représenter des entités, mais en fonction de votre domaine des relations peuvent être utilisées pour cet usage.

graphdb-nodes-overview.svg

Commençons avec un graphe super simple, contenant seulement un noeud avec une propriété:

graphdb-nodes.svg

Relations

Les relations entre les noeuds sont un élément clé d’une base de données graphe. Elles permettent de trouvent les données reliées. Tout comme les noeuds, les relations peuvent avoir des propriétés.

graphdb-rels-overview.svg

Une relation connecte deux noeuds en guarantissant d’avoir des noeuds de départ et d’arrivée valides.

graphdb-rels.svg

Du fait que les relations sont toujours directionnelles, elles peuvent être visualisées comme entrantes ou sortantes d’un noeud, ce qui est utile quand on traverse un graphe:

graphdb-rels-dir.svg

Les relations sont traversées aussi efficacement dans n’importe quelle direction. Cela signifie qu’il est nullement nécessaire d’ajouter un duplicata de la relation dans le sens opposé (en rapport avec les traversées ou la performance).

Bien que les relations ont toujours une direction, vous pouvez ignorer cette direction quand cela n’est pas nécessaire dans votre application.

Notez qu’un noeud peut également avoir des relations vers lui-même:

graphdb-rels-loop.svg

Pour améliore encore plus la traversée d’un graphe, toutes les relations ont un type de relation. Notez que le terme type peut être ambigü ici, vous pouvez plutôt penser à une étiquette. Les exemples suivants montrent un réseau social simple avec deux relations.

graphdb-rels-twitter.svg
Tableau 1. Utilisation d’une direction et type de relation
Quoi Comment

savoir qui une personne suit

relations suit sortantes, profondeur un

savoir les abonnés d’une personne

relations suit entrantes, profondeur un

savoir qui une personne bloque

relations bloque sortantes, profondeur un

savoir par qui une personne est bloquée

relations bloque entrantes, profondeur un

Cet exemple est un modèle simple d’un système de fichiers, qui inclut des liens symboliques:

graphdb-rels-filesys.svg

En fonction de ce que vous recherchez, vous utiliserez les directions et types de relations pendant les traversées.

Quoi Comment

chemin complet d’un fichier

relations fichier entrantes

tous les chemins d’un fichier

relations fichier et lien symbolique entrantes

tous les fichiers d’un dossier

relations fichier et lien symbolique sortantes, profondeur un

tous les fichiers d’un dossier, sauf les liens symboliques

relations fichier sortantes, profondeur un

tous les fichiers d’un dossier, récursivement

relations fichier et lien symbolique

Propriétés

Aussi bien les noeuds que les relations peuvent contenir des propriétés.

Les propriétés sont des paires clé-valeur où les clés sont des chaînes des caractères. Les valeurs peuvent être aussi bien primitives ou un tableau de types primitives. Par exemple les valeurs String, int et int[] sont des propriétés valides.

Note
null n’est pas une valeur de propriété valide. Null peut être modélisé par l’absence d’une clé.
graphdb-properties.svg
Tableau 2. Type de valeurs de propriétés
Type Déscription rayon de valeurs

boolean

true/false

byte

8-bit integer

-128 to 127, inclus

short

16-bit integer

-32768 to 32767, inclus

int

32-bit integer

-2147483648 to 2147483647, inclus

long

64-bit integer

-9223372036854775808 to 9223372036854775807, inclus

float

32-bit IEEE 754 nombre à virgule flottante

double

64-bit IEEE 754 nombre à virgule flottante

char

16-bit entiers non-signés représentant des caractères Unicode

u0000 à uffff (0 à 65535)

String

séquence de caractères Unicode

Pour plus de détails sur les valeurs flottantes/doubles, voyez Java Language Specification.

Chemins

Un chemin est un ou plusieurs noeuds avec des relations connectées, généralement récupérés comme résultat d’une requête ou traversée.

graphdb-path.svg

Le chemin le plus court possible a une longueur de zéro et ressemble à cela:

graphdb-path-example1.svg

Un chemin de longueur un:

graphdb-path-example2.svg

Another path of length one:

graphdb-path-example-loop.svg

Traversées

Traverser un graphe signifie visiter ses noeuds et suivre ses relations, cela en accordance avec certaines règles. Dans la plupart des cas, seulement un sous-graphe est visité du fait que vous connaissez déjà dans quelle partie du graphe se trouvent les noeuds et relations qui vous intéressent.

Neo4j est fourni avec une API basée sur des callbacks, ce qui vous laisse la possibilité de spécifier les règles pour les traversées. A un niveau basique, vous avez le choix entre les traversées «breadth» ou «depth-first».

Pour une introduction plus en détails du framework de traversées, voyez le [tutorial-traversal]. Pour des exemples de code Java, voyez [tutorials-java-embedded-traversal].

Les autres options pour traverser ou interroger un graphe dans Neo4j sont Cypher et Gremlin.

Opérations

Cette partie décrit comment procéder à l’installation de Neo4j et la maintenance de celle-ci. Elle inclut des chapîtres comme la sauvegarde de la base de données, le monitoring de sa santé ainsi que le diagnostic d’erreurs.

Installation & Déploiement

Scénarios de déploiements

Neo4j peut être embarqué dans votre application (Embedded), être exécuté comme serveur autonome (Neo4j Server) ou être déployé sur plusieurs machines afin de fournir une haute disponibilité.

Tableau 3. Neo4j deployment options
Instance Unique Instances Multiples

Embedded

EmbeddedGraphDatabase

HighlyAvailableGraphDatabase

Standalone

Neo4j Server

Neo4j Server high availability mode

Serveur

Neo4j est normalé accédé comme serveur autonome, soit directement via une interface ReST ou via un pilote de langage spécifique. Une information détaillée sur le serveur Neo4j est disponible ici [server]. Pour exécuter le serveur et les systèmes embarqués en mode haute disponibilié, voyez [ha].

Embarquée (Embedded)

Neo4j peut être embarqué directement sur un serveur d’applications en incluant les librairies Java appropriées. Pendant le développement, vous pouvez vous référer à l’API GraphDatabaseService . Pour basculer d’une instance unique vers le mode instances multiples haute disponibilité, il vous suffit simplement de basculer de la classe concrète EmbeddedGraphDatabase vers la HighlyAvailableGraphDatabase.

Configuration système requise

La mémoire limite la taille du graphe, les I/O disques limitent les performances lecture/écriture, comme toujours.

Processeur

La performance est généralement liée à la mémoire ou aux I/O pour de larges graphes, et calcule les limites du graphe qui pourraient être allouées en mémoire.

Minimum

Intel 486

Recommandé

Intel Core i7

Mémoire

Plus de mémoire permet de plus grands graphes, mais induit le risque de créer des opérations "Garbage Collection" assez larges.

Minimum

1GB

Recommandé

4-8GB

Disque

A côté de sa capacité, les caractéristiques de performances du disque sont un élément majeur quant au choix du type de stockage.

Minimum

SCSI, EIDE

Recommandé

SSD w/ SATA

Système de fichiers

Pour un comportement ACID sain, le système de fichiers doit supporter flush (fsync, fdatasync).

Minimum

ext3 (ou similaire)

Recommandé

ext4, ZFS

Logiciel

Neo4j est basé sur Java.

Java

Oracle Java 6

Système d’exploitation

Linux, Windows XP, Mac OS X

Version JDK

L’exécution de Neo4j est téstée continuellement avec

Installation

Neo4j peut être installé comme serveur, s’exécutant aussi bien en headless qu’en service système. Pour les développeurs Java, il est également possible d’utiliser Neo4j comme bibliothèque embarquée dans votre application.

Pour plus d’informations sur l’installation de Neo4j comme serveur, référez-vous à [server-installation].

Le tableau suivant énumère les éditions disponibles ainsi que leurs noms pour l’utilisation de celles-ci par des outils de gestion de dépendances.

Astuce
Suivez les liens dans le tableau pour plus de détails sur la configuration des dépendances avec Maven, Apache Buildr, Apache Ivy et Groovy Grape!
Tableau 4. Neo4j editions
Edition Dépendance Déscription Licence

Community

org.neo4j:neo4j

une base de données graphe ultra performante, système transactionnel ACID complet

GPLv3

Advanced

org.neo4j:neo4j-advanced

ajouté d’un système de monitoring avancé

AGPLv3

Enterprise

org.neo4j:neo4j-enterprise

ajouté d’un système de backup en ligne ainsi que de la haute disponibilité sur une grappe de serveurs (clustering)

AGPLv3

Note
Les dépendances listées ne contiennent pas l’implémentation, mais les ajoute transitivement.

For more information regarding licensing, see the Licensing Guide.

Installation Embarquée (Embedded)

La dernière version est toujours disponible depuis http://neo4j.org/download, avec les autres distributions disponibles. Après avoir sélectionné quelle était la version appropriée pour votre plateforme, embarquez Neo4j dans votre application en incluant le jars de la bibliothèque Neo4j à votre build. Vous pouvez soit copier les fichiers jar du dossier lib du paquet téléchargé, ou alors utiliser directement les artifacts disponibles depuis le Maven Central Repository
[http://repo1.maven.org/maven2/org/neo4j/]
. Les versions stables et incrémentielles sont disponibles à cet endroit.

Maven dependency
<project>
...
 <dependencies>
  <dependency>
   <groupId>org.neo4j</groupId>
   <artifactId>neo4j</artifactId>
   <version>1.9-SNAPSHOT</version>
  </dependency>
  ...
 </dependencies>
...
</project>

Where the artifactId is one of neo4j, neo4j-advanced, neo4j-enterprise.

Installation Serveur

Veuillez vous référer à [serveur] et [server-installation].

Tutorials

Cette partie du tutoriel explique comme mettre en place votre environnement et écrire des programmes utilisant Neo4j. Il vous guide du Hello World jusqu'à un usage avancé des graphes.

Using Neo4j embedded in Java applications

Embarquer Neo4j dans vos application Java est relativement simple. Dans ce chapitre vous trouverez tout le nécessaire, depuis la mise en service de l’environnement jusqu'à une manipulation utile de vos données.

Inclure Neo4j dans vos projets

Après avoir sélectionné l'édition appropriée pour votre plateforme, embarquez Neo4j dans votre application Java en incluant les jars de la libraire Neo4j dans votre build. Les sections suivantes expliquent comment réaliser cela soit en modifiant directement les chemins de votre build soit en utilisant un gestionnaire de dépendances.

Ajoutez Neo4j aux chemins de votre build

Récupérez Neo4j depuis l’une des sources suivantes:

  • Décompressez l’archive (zip/tarball) Neo4j download et utilisez les fiches jar se situant dans le dossier lib/.

  • Utilisez les fichiers jar disponibles depuis le Maven Central Repository

Ajoutez les fichiers jar à votre projet:

JDK tools

Ajoutez à -classpath

Eclipse
  • Cliquez droit sur le projet, ensuite choisissez Build Path → Configure Build Path. Dans la boîte de dialogue, choisissez Add External JARs, naviguez jusqu’au dossier lib/ de Neo4j et sélectionnez tous les fichiers jar.

  • Une autre options est d’utiliser les Bibliothèques Utilisateur.

IntelliJ IDEA

Voyez Bibliothèques, Bibliothèques globales et Configurer le dialogue Bibliothèque

NetBeans
  • Cliquez droit sur le noeud Bibliothèques de votre projet, choisissez Ajouter JAR/Dossier, naviguez jusqu’au dossier lib/ de Neo4j et sélectionnez tous les fichiers jar.

  • Vous pouvez également gérer les bibliothèques depuis le noeud du projet, voyez Gérer un Classpath projet.

Ajoutez Neo4j comme dépendance

Pour une vue globale des principaux artifacts Neo4j, voyez [editions]. Les artifacts listés sont des artifacts de haut niveau qui inclueront transitivement l’implémentation Neo4j actuelle. Vous pouvez utiliser directement l’artifact de haut niveau ou inclure directements les composants individuels. Les exemples présentés utilisent l’approche d’inclusion de l’artifact de haut niveau.

Maven
Maven dependency
<project>
...
 <dependencies>
  <dependency>
   <groupId>org.neo4j</groupId>
   <artifactId>neo4j</artifactId>
   <version>1.9-SNAPSHOT</version>
  </dependency>
  ...
 </dependencies>
...
</project>

artifactId est trouvé dans [editions].

Eclipse et Maven

Pour le développement dans Eclipse, il est recommandé d’installer le plugin m2e et de laisser Maven gérer le classpath du build du projet plutôt, voyez plus haut. Ceci ajoute également la possibilité de construire votre projet par la ligne de commande avec Maven tout en ayant un environnement Eclipse fonctionnel pour le développement.

Ivy

Assurez-vous de résoudre les dépendances depuis Maven Central, par exemple en utilisant cette configuration dans votre fichier ivysettings.xml:

Ivy
---
<ivysettings>
  <settings defaultResolver="main"/>
  <resolvers>
    <chain name="main">
      <filesystem name="local">
        <artifact pattern="${ivy.settings.dir}/repository/[artifact]-[revision].[ext]" />
      </filesystem>
      <ibiblio name="maven_central" root="http://repo1.maven.org/maven2/" m2compatible="true"/>
    </chain>
  </resolvers>
</ivysettings>
---

Avec ceci en place, vous pouvez ajouter Neo4j en ayant quelque chose de similaire à ces lignes dans votre fichier ivy.xml:

..
<dependencies>
  ..
  <dependency org="org.neo4j" name="neo4j" rev="1.9-SNAPSHOT"/>
  ..
</dependencies>
..

name est trouvé dans [editions].

Gradle

L’exemple ci-dessous montre un exemple d’un script de build gradle pour l’inclusion des bibliothèques Neo4j.

def neo4jVersion = "1.9-SNAPSHOT"
apply plugin: 'java'
repositories {
   mavenCentral()
}
dependencies {
   compile "org.neo4j:neo4j:${neo4jVersion}"
}

Où les coordonnées (org.neo4j:neo4j in the example) sont trouvées dans [editions].

Démarrage et arrêt

Pour créer une nouvelle base de données ou ouvrir une existante, vous instantiez une EmbeddedGraphDatabase.

graphDb = new GraphDatabaseFactory().newEmbeddedDatabase( DB_PATH );
registerShutdownHook( graphDb );
Note
L’instance EmbeddedGraphDatabase peut être partagée à travers plusieurs processus. Notez cependant que vous ne pouvez créer plusieurs instances pointant vers la même base de données.

Pour arrêter la base de données, appelez la méthode shutdown() :

graphDb.shutdown();

Afin de s’assurer que Neo4j est proprement arrêté, vous pouvez ajouter un hook d’arrêt:

private static void registerShutdownHook( final GraphDatabaseService graphDb )
{
    // Registers a shutdown hook for the Neo4j instance so that it
    // shuts down nicely when the VM exits (even if you "Ctrl-C" the
    // running application).
    Runtime.getRuntime().addShutdownHook( new Thread()
    {
        @Override
        public void run()
        {
            graphDb.shutdown();
        }
    } );
}

Si vous désirez une vue read-only de la base de données, utilisez EmbeddedReadOnlyGraphDatabase.

Pour démarrer avec des paramètres de configurations, un ficher de paramètres Neo4j peut être chargé comme ceci:

Traceback (most recent call last):
  File "/var/lib/jenkins/.asciidoc/filters/snippet/snippet.py", line 88, in 
    for line in snippet(**configuration(indata)):
  File "/var/lib/jenkins/.asciidoc/filters/snippet/snippet.py", line 41, in snippet
    sourceFile = open(PATH_PATTERN % locals())
IOError: [Errno 2] No such file or directory: 'target/test-sources/neo4j-examples-test-sources-jar/org/neo4j/examples/StartWithConfiguration.java'

Ou vous pouvez bien évidemment créer votre propre Map<String, String> automatiquement et utiliser celui-ci.

Pour les options de configuration, voyez [embedded-configuration].

Hello World

Apprenez comment créer et accéder aux noeuds et relations. Pour des informations sur la mise en place d’un projet, référez-vous à [tutorials-java-embedded-setup].

Rappelez-vous, dans Qu’est-ce qu’une base de données graphe, qu’un graphe Neo4j consiste en:

  • des Noeuds connectés par

  • des Relations avec

  • des Propriétés aussi bien pour les Noeuds que pour les Relations

Toutes les relations ont un type. Par exemple, si le graphe représente un réseau social, une relation pourrait être du type CONNAIT. Si une relation du type CONNAIT relie deux noeuds, cela représente probablement deux personnes qui se connaissent. Une grande partie de la sémantique (qui en est la définition) d’un graphe est encodée dans les types de relations de l’application. Et même si les relations sont orientées, elles sont traversées aussi efficacement dans n’importe laquelle des deux directions.

Astuce
Le code source de cet exemple peut être trouvé ici: EmbeddedNeo4j.java

Préparation de la base de données

Les types de relations peuvent être créés en utilisant un enum. Dans cet exemple nous aurons besoin d’un seul type de relation simple. Voici comment le définir:

private static enum RelTypes implements RelationshipType
{
    KNOWS
}

Nous préparons également quelques variables à utiliser:

GraphDatabaseService graphDb;
Node firstNode;
Node secondNode;
Relationship relationship;

La prochaine étape consiste à démarrer le serveur de la base de données. Notez que si le répertoire spécifié pour la base de données n’existe pas encore, il sera créé.

graphDb = new GraphDatabaseFactory().newEmbeddedDatabase( DB_PATH );
registerShutdownHook( graphDb );

Notez que démarrer le serveur de la base de données est une opération relativement coûteuse, donc ne démarrez pas une nouvelle instance à chaque fois que vous devez interagir avec la base de données! L’instance peut être partagée entre plusieurs processus. Les transactions sont confinées dans leur processus.

Comme vu précédemment, nous enregistrons un hook d’arrêt qui s’assurera que la base de données s’arrête à la sortie du JVM. Maintenant il est temps d’interagir avec la base de données.

Confinez vos écritures dans une transaction

Toutes les écritures (création, suppression et mise à jour de données) doivent être exéctutées dans une transaction. Ceci est une décision de design réfléchie, nous pensons vraiment que la démarquation des transactions est une partie importante quand on travaille avec une base de données vraiment orientée entreprises. Maintenant, la manipulation des transactions avec Neo4j est très simple:

Transaction tx = graphDb.beginTx();
try
{
    // Updating operations go here
    tx.success();
}
finally
{
    tx.finish();
}

Pour plus d’informations sur les transactions, voyez [transactions] et Java API for Transaction.

Création d’un petit graphe

Maintenant, créons quelques noeuds. L’API est très intuitive. N’hésitez pas à jeter un oeil aux Javadocs à http://components.neo4j.org/neo4j/1.9-SNAPSHOT/apidocs/. Elles sont également inclues dans la distribution. Voici comment créer un petit graphe constituant de deux noeuds, connectés entre eux par une relation et quelques propriétes:

firstNode = graphDb.createNode();
firstNode.setProperty( "message", "Hello, " );
secondNode = graphDb.createNode();
secondNode.setProperty( "message", "World!" );

relationship = firstNode.createRelationshipTo( secondNode, RelTypes.KNOWS );
relationship.setProperty( "message", "brave Neo4j " );

Nous avons maintenant un graphe qui ressemble à cela :

Hello-World-Graph-java.svg
Figure 7. Hello World Graph

Affichage du résultat

Après avoir créé notre graphe, voyons comme le lire et l’afficher.

System.out.print( firstNode.getProperty( "message" ) );
System.out.print( relationship.getProperty( "message" ) );
System.out.print( secondNode.getProperty( "message" ) );

Ce qui affichera:

Hello, brave Neo4j World!

Suppression de données

Dans ce cas, nous supprimons les données avant le commit:

// let's remove the data
firstNode.getSingleRelationship( RelTypes.KNOWS, Direction.OUTGOING ).delete();
firstNode.delete();
secondNode.delete();

Notez que supprimer un noeud qui contient toujours des relations au commit de la transaction résultera en un échec. Ceci est pour s’assurer que les relations aient toujours un noeud de départ de d’arrivée.

Arrêt du serveur de la base de données

Enfin, arrêtez le serveur de la base de données quand l’application se termine:

graphDb.shutdown();

Base de données d’utilisateurs avec index

Vous avez une base de données d’utilisateurs et vous voulez retrouver les utilisateurs par nom. Pour commencer, voici la structure de la base de données que nous voulons créer:

users.png
Figure 8. Node space view of users

Donc, le noeud de référence est connecté à une référence de noeuds d’utilisateurs à qui sont connectés tous les noeuds utilisateurs.

Astuce
Le code source de cet exemple peut être trouvé ici: EmbeddedNeo4jWithIndexing.java

Pour commencer, nous définissons les types de relations que nous voulons utiliser:

Traceback (most recent call last):
  File "/var/lib/jenkins/.asciidoc/filters/snippet/snippet.py", line 88, in 
    for line in snippet(**configuration(indata)):
  File "/var/lib/jenkins/.asciidoc/filters/snippet/snippet.py", line 67, in snippet
    raise ValueError('Missing snippet for tag "' + tag + '" in file "' + source + '" in component "' + component +'" with classifier "' + classifier + '".')
ValueError: Missing snippet for tag "createRelTypes" in file "org/neo4j/examples/EmbeddedNeo4jWithIndexing.java" in component "neo4j-examples" with classifier "sources".

Ensuite nous créeons deux méthodes d’aider pour gérer les noms d’utilisateurs et ajouter les utilisateurs à la base de données:

private static String idToUserName( final int id )
{
    return "user" + id + "@neo4j.org";
}

private static Node createAndIndexUser( final String username )
{
    Node node = graphDb.createNode();
    node.setProperty( USERNAME_KEY, username );
    nodeIndex.add( node, USERNAME_KEY, username );
    return node;
}

La prochaine étape consiste à démarrer le serveur de la base de données:

graphDb = new GraphDatabaseFactory().newEmbeddedDatabase( DB_PATH );
nodeIndex = graphDb.index().forNodes( "nodes" );
registerShutdownHook();

Il est temps d’ajouter les utilisateurs:

Transaction tx = graphDb.beginTx();
try
{
    // Create some users and index their names with the IndexService
    for ( int id = 0; id < 100; id++ )
    {
        Node userNode = createAndIndexUser( idToUserName( id ) );
    }

Et voici comment retrouver un utilisateur par son Id:

int idToFind = 45;
String userName = idToUserName( idToFind );
Node foundUser = nodeIndex.get( USERNAME_KEY, userName ).getSingle();
System.out.println( "The username of user " + idToFind + " is "
    + foundUser.getProperty( USERNAME_KEY ) );

Tests unitaires de base

Le modèle basique de test unitaire avec Neo4j est illustré par l’exemple suivant.

Afin d’accéder aux facilités de tests Neo4j vous devez avoir le neo4j-kernel tests.jar au classpath pendant les tests. Vous pouvez le télécharger depuis le Maven Central: org.neo4j:neo4j-kernel.

En utilisant Maven comme gestionnaire de dépendances, vous ajouteriez cette dépendance comme ceci:

To access the Neo4j testing facilities you should have the neo4j-kernel tests.jar on the classpath during tests. You can download it from Maven Central: org.neo4j:neo4j-kernel.

Maven dependency
<project>
...
 <dependencies>
  <dependency>
   <groupId>org.neo4j</groupId>
   <artifactId>neo4j-kernel</artifactId>
   <version>${neo4j-version}</version>
   <type>test-jar</type>
   <scope>test</scope>
  </dependency>
  ...
 </dependencies>
...
</project>

${neo4j-version} est la version désirée de Neo4j.

Avec ceci en place, nous sommes prêts à coder nos tests.

Astuce
Pour le code complet de cet exemple, voyez: Neo4jBasicTest.java

Avant chaque test, créez une base de données saine:

@Before
public void prepareTestDatabase()
{
    graphDb = new TestGraphDatabaseFactory().newImpermanentDatabase();
}

Après que les tests aient été exécutés, la base de données doit être arrêtée:

@After
public void destroyTestDatabase()
{
    graphDb.shutdown();
}

Pendant un test, créez des noeuds et vérifiez qu’ils sont bien présents en accolant vos opérations d'écriture dans une transaction.

Transaction tx = graphDb.beginTx();

Node n = null;
try
{
    n = graphDb.createNode();
    n.setProperty( "name", "Nancy" );
    tx.success();
}
catch ( Exception e )
{
    tx.failure();
}
finally
{
    tx.finish();
}

// The node should have an id greater than 0, which is the id of the
// reference node.
assertThat( n.getId(), is( greaterThan( 0l ) ) );

// Retrieve a node by using the id of the created node. The id's and
// property should match.
Node foundNode = graphDb.getNodeById( n.getId() );
assertThat( foundNode.getId(), is( n.getId() ) );
assertThat( (String) foundNode.getProperty( "name" ), is( "Nancy" ) );

Si vous désirez spécifier des options de configuration pendant la création de la base de données, c’est réalise comme cela:

Map config = new HashMap();
config.put( "neostore.nodestore.db.mapped_memory", "10M" );
config.put( "string_block_size", "60" );
config.put( "array_block_size", "300" );
GraphDatabaseService db = new TestGraphDatabaseFactory()
    .newImpermanentDatabaseBuilder()
    .setConfig( config )
    .newGraphDatabase();

Traversier

Pour des informations sur le traversier, voyez [tutorial-traversal].

Pour plus d’exemples sur le traversier, voyez [data-modeling-examples].

La Matrice

Les parcours (traversées) des exemples de Matrice ci-dessus, cette fois en utilisant la nouvelle API du Traversier:

Astuce
Le code source des exemples peut être trouvé ici: NewMatrix.java
private static Traverser getFriends(
        final Node person )
{
    TraversalDescription td = Traversal.description()
            .breadthFirst()
            .relationships( RelTypes.KNOWS, Direction.OUTGOING )
            .evaluator( Evaluators.excludeStartPosition() );
    return td.traverse( person );
}

Exécutons ce parcours et affichons le résultat:

int numberOfFriends = 0;
String output = neoNode.getProperty( "name" ) + "'s friends:\n";
Traverser friendsTraverser = getFriends( neoNode );
for ( Path friendPath : friendsTraverser )
{
    output += "At depth " + friendPath.length() + " => "
              + friendPath.endNode()
                      .getProperty( "name" ) + "\n";
    numberOfFriends++;
}
output += "Number of friends found: " + numberOfFriends + "\n";

Ce qui nous donnera la sortie suivante:

Thomas Anderson's friends:
At depth 1 => Trinity
At depth 1 => Morpheus
At depth 2 => Cypher
At depth 3 => Agent Smith
Number of friends found: 4
private static Traverser findHackers( final Node startNode )
{
    TraversalDescription td = Traversal.description()
            .breadthFirst()
            .relationships( RelTypes.CODED_BY, Direction.OUTGOING )
            .relationships( RelTypes.KNOWS, Direction.OUTGOING )
            .evaluator(
                    Evaluators.includeWhereLastRelationshipTypeIs( RelTypes.CODED_BY ) );
    return td.traverse( startNode );
}

Affichage du résultat:

String output = "Hackers:\n";
int numberOfHackers = 0;
Traverser traverser = findHackers( getNeoNode() );
for ( Path hackerPath : traverser )
{
    output += "At depth " + hackerPath.length() + " => "
              + hackerPath.endNode()
                      .getProperty( "name" ) + "\n";
    numberOfHackers++;
}
output += "Number of hackers found: " + numberOfHackers + "\n";

Maintenant nous savons qui a codé la Matrice:

Hackers:
At depth 4 => The Architect
Number of hackers found: 1
Parcours d’un chemin orienté

Cet exemple montre comment utiliser un contexte de chemin contenant une représentation de celui-ci.

Astuce
Le code source de cet exemple peut-être trouvé ici: OrderedPath.java
Node A = db.createNode();
Node B = db.createNode();
Node C = db.createNode();
Node D = db.createNode();
A.createRelationshipTo( B, REL1 );
B.createRelationshipTo( C, REL2 );
C.createRelationshipTo( D, REL3 );
A.createRelationshipTo( C, REL2 );
example-ordered-path.svg

Maintenant, l’ordre des relations (REL1REL2REL3) est enregistré dans ArrayList. Au parcours, l' Evaluator peut l’utiliser comme moyen de vérification afin de s’assurer que seuls les chemins ayant cet ordre prédéfini de relations seront inclus et retournés:

final ArrayList orderedPathContext = new ArrayList();
orderedPathContext.add( REL1 );
orderedPathContext.add( withName( "REL2" ) );
orderedPathContext.add( withName( "REL3" ) );
TraversalDescription td = Traversal.description()
        .evaluator( new Evaluator()
        {
            @Override
            public Evaluation evaluate( final Path path )
            {
                if ( path.length() == 0 )
                {
                    return Evaluation.EXCLUDE_AND_CONTINUE;
                }
                RelationshipType expectedType = orderedPathContext.get( path.length() - 1 );
                boolean isExpectedType = path.lastRelationship()
                        .isType( expectedType );
                boolean included = path.length() == orderedPathContext.size()
                                   && isExpectedType;
                boolean continued = path.length() < orderedPathContext.size()
                                    && isExpectedType;
                return Evaluation.of( included, continued );
            }
        } );
Traverser traverser = td.traverse( A );
PathPrinter pathPrinter = new PathPrinter( "name" );
for ( Path path : traverser )
{
    output += Traversal.pathToString( path, pathPrinter );
}

Ce qui affichera:

(A)--[REL1]-->(B)--[REL2]-->(C)--[REL3]-->(D)

Nous utilisons ici une classe personalisée afin de formatter la sortie du chemin. C’est réalisé comme cela:

static class PathPrinter implements Traversal.PathDescriptor
{
    private final String nodePropertyKey;

    public PathPrinter( String nodePropertyKey )
    {
        this.nodePropertyKey = nodePropertyKey;
    }

    @Override
    public String nodeRepresentation( Path path, Node node )
    {
        return "(" + node.getProperty( nodePropertyKey, "" ) + ")";
    }

    @Override
    public String relationshipRepresentation( Path path, Node from,
            Relationship relationship )
    {
        String prefix = "--", suffix = "--";
        if ( from.equals( relationship.getEndNode() ) )
        {
            prefix = "<--";
        }
        else
        {
            suffix = "-->";
        }
        return prefix + "[" + relationship.getType().name() + "]" + suffix;
    }
}

Pour des options en rapport avec la sortie d’un Chemin, reportez-vous à la classe Traversal.

Note
Les exemples suivants utilisent une version dépréciée de l’API du Traversier. Ils partagent l’implémentation en bas niveau de la nouvelle API du Traversier, ainsi ils sont égals au niveau performance. Les fonctionnalités qu’elle propose sont cependant limitées comparativement.

Ancienne API du Traversier

C’est ici le premier graphe dans lequel nous voulons parcourir:

examples-matrix.png
Figure 9. Matrix node space view
Astuce
Le code source de cet exemple peut être trouvé ici: Matrix.java
private static Traverser getFriends( final Node person )
{
    return person.traverse( Order.BREADTH_FIRST,
            StopEvaluator.END_OF_GRAPH,
            ReturnableEvaluator.ALL_BUT_START_NODE, RelTypes.KNOWS,
            Direction.OUTGOING );
}

Exécutons un parcours et affichons le résultat:

int numberOfFriends = 0;
String output = neoNode.getProperty( "name" ) + "'s friends:\n";
Traverser friendsTraverser = getFriends( neoNode );
for ( Node friendNode : friendsTraverser )
{
    output += "At depth " +
                friendsTraverser.currentPosition().depth() +
                " => " +
                friendNode.getProperty( "name" ) + "\n";
    numberOfFriends++;
}
output += "Number of friends found: " + numberOfFriends + "\n";

Ce qui donnera l’affichage suivant:

Thomas Anderson's friends:
At depth 1 => Trinity
At depth 1 => Morpheus
At depth 2 => Cypher
At depth 3 => Agent Smith
Number of friends found: 4
private static Traverser findHackers( final Node startNode )
{
    return startNode.traverse( Order.BREADTH_FIRST,
            StopEvaluator.END_OF_GRAPH, new ReturnableEvaluator()
    {
        @Override
        public boolean isReturnableNode(
                final TraversalPosition currentPos )
        {
            return !currentPos.isStartNode()
            && currentPos.lastRelationshipTraversed()
            .isType( RelTypes.CODED_BY );
        }
    }, RelTypes.CODED_BY, Direction.OUTGOING, RelTypes.KNOWS,
    Direction.OUTGOING );
}

Affichons le résultat:

String output = "Hackers:\n";
int numberOfHackers = 0;
Traverser traverser = findHackers( getNeoNode() );
for ( Node hackerNode : traverser )
{
    output += "At depth " +
                traverser.currentPosition().depth() +
                " => " +
                hackerNode.getProperty( "name" ) + "\n";
    numberOfHackers++;
}
output += "Number of hackers found: " + numberOfHackers + "\n";

Nous savons maintenant qui a codé la Matrice:

Hackers:
At depth 4 => The Architect
Number of hackers found: 1

Unicité des chemins dans les parcours de graphe

Cet exemple démontre l’utilisation de noeuds uniques. Partons d’un graphe imaginaire de Particuliers qui détentent des Animaux qui eux-mêmes sont descendants d’autres Animaux.

Note
Le nom des variables dans les exemples sont en Anglais, le code des exemples provient de tests unitaires et est donc tout le temps à jour.
Descendants-Example-Graph-Uniqueness-of-Paths-in-traversals.svg
Figure 10. Exemple de Graphe de Descendants

Afin de retourner tous les descendants de Pet0 qui a une relation owns avec Principal1 (Pet1 et Pet3), l’Unicité du parcours a besoin d'être défini à NODE_PATH plutôt qu'à NODE_GLOBAL ansi les noeuds peuvent être traversés plus d’une fois et les chemins qui ont des noeuds différents et aussi communs (comme le noeud de départ et d’arrivée) peuvent etre retournés.

final Node target = data.get().get( "Principal1" );
TraversalDescription td = Traversal.description()
        .uniqueness( Uniqueness.NODE_PATH )
        .evaluator( new Evaluator()
{
    @Override
    public Evaluation evaluate( Path path )
    {
        if ( path.endNode().equals( target ) )
        {
            return Evaluation.INCLUDE_AND_PRUNE;
        }
        return Evaluation.EXCLUDE_AND_CONTINUE;
    }
} );

Traverser results = td.traverse( start );

Ceci retournera les chemins suivants:

(3)--[descendant,0]-->(1)<--[owns,3]--(5)
(3)--[descendant,2]-->(4)<--[owns,5]--(5)

Dans l’implémentation par défaut path.toString(), (1)--[knows,2]-->(4) dénote un noeuds avec un ID=1 ayant une relation avec pour ID=2 ou un type=KNOWS vers un noeud qui a pour ID=4.

Créons maintenant une nouvelle Déscription de Parcous en rapport avec l’ancien, ayant une unicité sur NODE_GLOBAL pour voir la différence.

Note
L’objet TraversalDescription est immuable, nous devons donc utiliser la nouvelle instance retournée avec le nouveau paramètre d’unicité.
TraversalDescription nodeGlobalTd = td.uniqueness( Uniqueness.NODE_GLOBAL );
results = nodeGlobalTd.traverse( start );

Maintenant, un seul chemin est retourné:

(3)--[descendant,0]-->(1)<--[owns,3]--(5)

Cypher Query Language

Cypher est un langage de requêtage déclaratif. Il permet d’effectuer des requêtes et mises jour du graphe efficaces sans avoir écrire de traversiers. Cypher is encore en phase d'évolution et de maturation, ce qui signifie qu’il y aura certainement des changes de syntaxe qui ne seront pas rétro compatibles. Cela signifie aussi que Cypher n’a pas encore t soumis aux tests de performance rigoureux comme les autres composants Neo4j.

Cypher est destiné à être un langage de requêtage humain, approprié aussi bien aux développeurs qu’aux ( et cela nous semble important ) testeurs qui veulent faire des requêtes ad-hoc sur la base de données. Notre ligne de conduite est de rendre simple les choses simples et possible les choses complexes. Sa construction est basée sur la prose Anglaise et une iconographie innée, ce qui aide à rendre ce langage auto-descriptif.

Cypher est inspiré de plusieurs approches et construit sur des pratiques établies pour le requêtage expressif. La plupart des mots clefs comme WHERE et ORDER BY sont inspirés de SQL. La concordance de patterns emprunte les approches d’expression de SPARQL.

En étant un langage déclaratif, Cypher se concentre sur la clarté d’exprimer quoi retrouver dans un graphe et non comment le faire. Ceci est en contraste aux langages impératifs comme Java et aux langages script comme Gremlin (supporté via le [gremlin-plugin]) et the JRuby Neo4j bindings. Ce qui rend le fait d’optimisation de requêtes un détail d’implémentation non exposé aux utilisateurs.

Le langage de requêtage comporte un nombre de clauses distinctes.

  • START: Points de départ dans le graphe, obtenus via un lookup d’index ou via des ID d'éléments.

  • MATCH: Le pattern du graphe à correspondre, lié aux points de départ dans START.

  • WHERE: Critère de filtrage.

  • RETURN: Ce qu’il faut retourner.

  • CREATE: Crée des noeuds ou des relations.

  • DELETE: Efface des noeuds, relations ou propriétés.

  • SET: Affecte des valeurs aux propriétés.

  • FOREACH: Effectue des actions de mise à jour une fois par élément dans une liste.

  • WITH: Divise une requête en de multiples parties distinctes.

Voyons voir trois d’entre elles en action.

Imaginez un exemple de graphe comme le suivant:

Example-Graph-cypher-intro.svg
Figure 11. Graphe Exemple

Par exemple, ici une requête qui trouve un utilisateur nommé John dans un index et traverse le graphe afin de trouver les amis de John (mais pas ses amis directs) en retournant John et les amis des amis de John.

START john=node:node_auto_index(name = 'John')
MATCH john-[:friend]->()-[:friend]->fof
RETURN john, fof

Résultant en:

johnfof
2 rows
2 ms

Node[4]{name:"John"}

Node[2]{name:"Maria"}

Node[4]{name:"John"}

Node[3]{name:"Steve"}

A présent, nous allons ajouter un filtrage afin de voir plus de parties en mouvement:

Dans cet exemple, nous prenons une liste d’utilisateurs (par ID de noeud) et traversons le graphe en recherchant les autres utilisateurs qui ont une relation sortante friend, retournant seulement les utilisateurs parcourus qui ont une propriété name commençant par un S.

START user=node(5,4,1,2,3)
MATCH user-[:friend]->follower
WHERE follower.name =~ 'S.*'
RETURN user, follower.name

Résultant en:

userfollower.name
2 rows
1 ms

Node[5]{name:"Joe"}

"Steve"

Node[4]{name:"John"}

"Sara"

Pour utiliser Cypher depuis Java, voyez [tutorials-cypher-java]. Pour plus d’exemples Cypher, voyez aussi [data-modeling-examples].

Opérateurs

Mathematical operators

The mathematical operators are +, -, *, / and %.

Comparison operators

The comparison operators are =, <>, <, >, <=, >=.

String operators

Strings can be concatenated using the + operator.

Collection operators

Collections can be concatenated using the + operator.

Property operators

Since Neo4j is a schema-free graph database, Cypher has two special operators: ? and !.

They are used on properties, and are used to deal with missing values. A comparison on a property that does not exist would normally cause an error. Instead of having to always check if the property exists before comparing its value with something else, the special property operators can be used. The question mark makes the comparison always return true if the property is missing, and the exclamation mark makes the comparator return false.

Ce prédicat évaluera à vrai si n.prop est manquant.

WHERE n.prop? = "foo"

Ce prédicat évaluera à faux si +n.prop. est manquant.

WHERE n.prop! = "foo"

Attention
Mixer les deux dans une même comparaison peut mener à des résultats non prévisibles.

C’est vraiment un jeu d’enfant quand on voit ce qui en ressort:

WHERE n.prop? = "foo"WHERE (not(has(n.prop)) OR n.prop = "foo")

WHERE n.prop! = "foo"WHERE (has(n.prop) AND n.prop = "foo")

Expressions

Expressions in general

Une expression dans Cypher peut être:

  • Un littérale numérique (integer ou double): 13, 40000, 3.14.

  • Une chaîne littérale: "Hello", 'World'.

  • Un booléen littérale: true, false, TRUE, FALSE.

  • Un identifiant: n, x, rel, myFancyIdentifier, `Un nom avec plein de jam dedans[]!`.

  • Une propriété: n.prop, x.prop, rel.thisProperty, myFancyIdentifier.`(nom de propriété bizarre)`.

  • Une propriété qui peut être null: C’est une propriété avec un point d’interrogation ou un point d’exclamation — n.prop?, rel.thisProperty!.

  • Un paramètre: {param}, {0}

  • Une collection d’expressions: ["a", "b"], [1,2,3], ["a", 2, n.property, {param}], [ ].

  • Un appel de fonction: length(p), nodes(p).

  • Une fonction d’agrégation: avg(x.prop), count(*).

  • Des types de relations: :REL_TYPE, :`REL TYPE`, :REL1|REL2.

  • Un pattern de chemin: a-->()<--b.

  • Un prédication d’expression est une expression qui retourne vrai ou faux: a.prop = "Hello", length(p) > 10, has(a.name)

Note sur les chaînes littérales

Les chaînes littérales peuvent contenir ces séquences d'échappement.

Séquence d'échappement Caractère

\t

Tabulation

\b

Retour

\n

Nouvelle ligne

\r

Retour chariot

\f

Form feed

\'

Guillemet simple

\"

Guillemet double

\\

Barre oblique inversée

Paramètres

Cypher supporte le requêtage avec paramètres. Cela permet aux développeurs de ne pas devoir construire de chaînes afin de créer une requête, cela facilite également la mise en cache de plans d’exécution pour Cypher.

Les paramètres peuvent être utilisés pour des littérales et expressions dans la clause WHERE, pour la clé d’index et pour la valeur d’index dans la clause START, les requêtes d’index et finalement pour les id de noeuds/relations. Les paramètres ne peuvent pas être utilisés pour les noms de propriétés du fait que la notation de propriétés fait partie de la structure de requête qui est compilée dans le plan de requête.

Les noms de paramètres peuvent contenir des lettres, des chiffres et n’importe quelle combinaison de ceux-ci.

Les exemples suivants démontrent comment vous pouvez utiliser les paramètres en Java.

Map params = new HashMap();
params.put( "id", 0 );
ExecutionResult result = engine.execute( "start n=node({id}) return n.name", params );
Map params = new HashMap();
params.put( "node", andreasNode );
ExecutionResult result = engine.execute( "start n=node({node}) return n.name", params );
Map params = new HashMap();
params.put( "id", Arrays.asList( 0, 1, 2 ) );
ExecutionResult result = engine.execute( "start n=node({id}) return n.name", params );
Map params = new HashMap();
params.put( "name", "Johan" );
ExecutionResult result =
        engine.execute( "start n=node(0,1,2) where n.name = {name} return n", params );
Map params = new HashMap();
params.put( "key", "name" );
params.put( "value", "Michaela" );
ExecutionResult result =
        engine.execute( "start n=node:people({key} = {value}) return n", params );
Map params = new HashMap();
params.put( "query", "name:Andreas" );
ExecutionResult result = engine.execute( "start n=node:people({query}) return n", params );
Map params = new HashMap();
params.put( "s", 1 );
params.put( "l", 1 );
ExecutionResult result =
        engine.execute( "start n=node(0,1,2) return n.name skip {s} limit {l}", params );
Map params = new HashMap();
params.put( "regex", ".*h.*" );
ExecutionResult result =
        engine.execute( "start n=node(0,1,2) where n.name =~ {regex} return n.name", params );
Map n1 = new HashMap();
n1.put( "name", "Andres" );
n1.put( "position", "Developer" );

Map params = new HashMap();
params.put( "props", n1 );
engine.execute( "START n=node(0) SET n = {props}", params );

Identifiants

Quand vous référencez des parties du pattern, vous le faites en les nommant. Les noms que vous donnez aux différentes parties sont appelés des identifiants.

Dans cet exemple:

START n=node(1) MATCH n-->b RETURN b

Les identifiants sont n et b.

Les noms d’identifiants sont sensibles à la casse et peuvent contenir des underscores et des caractères alphanumériques (a-z, 0-9) mais doivent commencer par une lettre. Si d’autres caractères sont nécessaires, vous pouvez utiliser l’identifiant en utilisant le guillemet inverse (`).

Les mêmes règles s’appliquent aux noms de propriétés.

Commentaires

Pour ajouter des commentaires dans vos requêtes, utilisez des doubles barres obliques. Exemples:

START n=node(1) RETURN n //This is an end of line comment
START n=node(1)
//This is a whole line comment
RETURN n
START n=node(1) WHERE n.property = "//This is NOT a comment" RETURN n

Mise à jour du graphe

Cypher peut être utilisé aussi bien pour requêter que pour mettre à jour le graphe.

La Structure de Requêtes de Mise à Jour

Info flash
  • Une partie de requête Cypher peut très bien trouver dans le graphe et le mettre à jour en même temps.

  • Chaque partie peut également lire et trouver dans le graphe, ou même le mettre à jour.

Si vous lisez dans le graphe et que vous le mettez ensuite à jour, votre requête contient implicitement deux parties — la lecture est la première partie et l'écriture la seconde. Si votre requête est une simple lecture, Cypher sera fainéant et ne fera pas de pattern matching tant que vous ne demandiez de résultats en retour. Ici la sémantique est que toutes les lectures seront effectuées avant n’importe quelle écriture. Ceci est très important! Sans cela il est facile de trouver des cas où le pattern matcher rencontrera des données qui ont été créées par cette même requête et toutes les prédictions sont réduites à néant. Cette route mène à Heisenbugs, au mouvement Brownien et les chats qui sont morts et en vie en même temps.

D’abord lire, ensuite écrire est le seul pattern où les parties de requêtes sont implicites — tout autre ordre et vous devez être explicite à propos de vos parties de requêtes. Les parties sont séparées en utilisant le WITH. WITH est comme l’horizon — c’est une barrière entre un plan et l’exécution de ce plan.

Quand vous voulez filtrer en utilisant des données agrégées, vous devez lier ensemble deux parties de requêtes — la première fait l’agrégation et la suivant filtre sur les données venant de la première.

START n=node(...)
MATCH n-[:friend]-friend
WITH n, count(friend) as friendsCount
WHERE friendsCount > 3
RETURN n, friendsCount

En utilisant le WITH, vous spécifiez comment vous voulez que l’agrégation se fasse et cette agrégation doit être finie avant que Cypher commence son filtrage.

Vous pouvez lier ensemble autant de parties de requêtes que vous désirez si vous avez un heap JVM pour.

Retourner des données

Chaque requête peut retourner des données. Si votre requête ne fait que lire, elle doit retourner des données — cela n’a pas de but si elle ne retourne rien et ne sera pas considérée comme requête Cypher valide. Les requêtes qui mettent à jour le graphe ne doivent pas nécessairement retourner des données, mais peuvent. Après toutes les parties d’une requête vient le déclaratif final RETURN. RETURN ne fait partie d’aucune partie de requête — c’est une virgule après les parties de requête. Quand RETURN est autorisé, il est aussi autorisé d’utiliser SKIP/LIMIT et ORDER BY.

Si vous retournez des éléments d’un graphe qui viennent juste d'être supprimés — faites attention, vous détenez un pointeur qui n’est plus valide. Des opérations sur ce noeud peuvent échouer mystérieusement et imprévisiblement.

Transactions

Chaque requête qui met le graphe à jour doivent s’effectuer dans une transaction. Chaque requête de mise à jour sera toujours soit complètement réussie, soit complètement échouée.

Cypher créera une nouvelle transaction et la commitera une fois que la requête est terminée. Ou si une transaction existe déjà dans le contexte courant, la requête sera exécutée dedans et rien ne sera persisté sur le disque tant que la transaction n’est pas commitée.

Ceci peut être utilisé afin d’avoir plusieurs requêtes committées comme une seule transaction:

  1. Ouvrir une transaction,

  2. effectue plusieurs requêtes Cypher de mise à jour

  3. et les commiter toutes en même temps.

Notez qu’une requête retiendra tous les changements dans le heap tant que toute la requête ait fini de s’exécuter. Une grande requête aura pour conséquence de nécessiter une JVM avec beaucoup d’espace de heap.

Patterns

Les patterns sont au coeur de Cypher et sont utilisés à beaucoup d’endroits. En utilisant les patterns, vous décrivez la forme des données que vous recherchez. Les patterns sont utilisés dans la clause MATCH. Les patterns de chemin sont des expressions. Comme ces expressions sont des collections, elles peuvent également être utilisées comme prédicats (une collection non vide signifie vrai). Ils sont également utilisés pour CREATE/CREATE UNIQUE dans le graphe.

Donc, la compréhension des patterns est important afin d'être efficace avec Cypher.

Vous décrivez le pattern et Cypher déterminera comment retrouver les données pour vous. L’idée pour vous est de dessiner la requête sur un tableau blanc, de nommer les parties intéressantes du pattern, ainsi vous pouvez utiliser les valeurs de ces parties pour créer le jeu de résultats que vous recherchez.

Les patterns ont des points de liaison ou des points de départ. Ils sont les parties du pattern qui ont déjà une ``liaison`` avec un jeu de noeuds ou relations. Toutes les parties du pattern doivent être directement ou indirectement connectées à un point de départ — un pattern où les parties du pattern ne sont pas joignables depuis un point de départ seront rejetés.

Clause Optionnel Types de rel. multiples Longueur variable Chemins Maps

Match

Oui

Oui

Oui

Oui

-

Create

-

-

-

Oui

Oui

Create Unique

-

-

-

Oui

Oui

Expressions

-

Oui

Oui

-

-

Patterns pour les noeuds liés

La description des patterns est composée d’un ou plusieurs chemins, séparés par une virgule. Un chemin est une séquence de noeuds et relations qui démarrent et finissent toujours dans des noeuds. Un exemple serait:

(a)-->(b)

C’est un chemin partant du pattern node a, avec une relation sortante vers le pattern node b.

Les chemins peuvent être d’une longueur arbitraire, et le même noeud peut apparaître dans des endroits multiples de ce chemin.

Les identifiants de noeuds peuvent être utilisés avec ou sans parenthèses. La correspondance suivante est sémantique identique à celle que nous avons vu plus haut — la différence est purement esthétique.

a-->b

Si un noeud ne vous intéresse pas, vous n'êtes pas obligés de le nommé. Les parenthèses vides sont utilisées pour ces noeuds, comme ceci:

a-->()<--b

Travailler avec des relations

Si vous devez travailler avec des relations entre deux noeuds, vous pouvez les nommer.

a-[r]->b

Si la direction de la relation n’est pas importante, vous pouvez omettre la flèche à n’importe quelle fin de la relation, comme ceci:

a--b

Les relations ont des types. Quand vous n'êtes intéressés que par un type de relations spécifique, vous pouvez le spécifier comme ceci:

a-[:REL_TYPE]->b

Si plusieurs types de relations sont acceptables, vous pouvez les lister en les séparant avec le symbol pipe | comme ceci:

a-[r:TYPE1|TYPE2]->b

Ce pattern fera correspondre une relation de type TYPE1 ou TYPE2, allant de a à b. La relation est nommée r. Des types de relations multiples ne peuvent pas êtres utilisés avec CREATE ou CREATE UNIQUE.

Relations optionnelles

Une relation optionnelle est retournée si elle est trouvée, sinon elle sera remplacée par null. Normalement, si aucune relation correspondante n’est trouvée, ce sous-graphe n’est pas retourné. Des relations optionnelles dans Cypher peuvent être comparées aux outer join dans SQL.

Elles ne peuvent être utilisées que dans MATCH.

Les relations optionnelles sont marquées avec un point d’interrogation. Elles permettent d'écrire des requêtes comme ceci:

Requête
START me=node(*)
MATCH me-->friend-[?]->friend_of_friend
RETURN friend, friend_of_friend

Essayez cette requête en live(1) {"name":"Dilshad"} (2) {"name":"Emil"} (3) {"name":"Filipa"} (4) {"name":"Anders"} (5) {"name":"Becky"} (6) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (4)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(6) {} (4)-[:KNOWS]->(1) {} (5)-[:KNOWS]->(2) {} (6)-[:KNOWS]->(2) {} START me=node(*) MATCH me-->friend-[?]->friend_of_friend RETURN friend, friend_of_friend

La requête plus haut dit ``pour chaque personne, donnes moi tous ses amis et les amis de ses amis, si ils en ont.``

L’option est transitive — si une partie du pattern peut seulement être atteinte par un point lié via une relation optionnelle, cette partie est aussi optionnelle. Dans le pattern ci-dessus, le seul point lié dans le pattern est me. Du fait que les relations entre friend et children sont optionnelles, children est une partie optionnelle du graphe.

Les chemins qui contiennent des parties optionnelles sont également optionnels — si une partie du chemin est null, tout le chemin sera null.

Dans les exemples suivants, b et p sont tous optionnels et peuvent contenir null:

Requête
START a=node(4)
MATCH p = a-[?]->b
RETURN b

Essayez cette requête en live(1) {"name":"Dilshad"} (2) {"name":"Emil"} (3) {"name":"Filipa"} (4) {"name":"Anders"} (5) {"name":"Becky"} (6) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (4)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(6) {} (4)-[:KNOWS]->(1) {} (5)-[:KNOWS]->(2) {} (6)-[:KNOWS]->(2) {} START a=node(4) MATCH p = a-[?]->b RETURN b

Requête
START a=node(4)
MATCH p = a-[?*]->b
RETURN b

Essayez cette requête en live(1) {"name":"Dilshad"} (2) {"name":"Emil"} (3) {"name":"Filipa"} (4) {"name":"Anders"} (5) {"name":"Becky"} (6) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (4)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(6) {} (4)-[:KNOWS]->(1) {} (5)-[:KNOWS]->(2) {} (6)-[:KNOWS]->(2) {} START a=node(4) MATCH p = a-[?*]->b RETURN b

Requête
START a=node(4)
MATCH p = a-[?]->x-->b
RETURN b

Essayez cette requête en live(1) {"name":"Dilshad"} (2) {"name":"Emil"} (3) {"name":"Filipa"} (4) {"name":"Anders"} (5) {"name":"Becky"} (6) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (4)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(6) {} (4)-[:KNOWS]->(1) {} (5)-[:KNOWS]->(2) {} (6)-[:KNOWS]->(2) {} START a=node(4) MATCH p = a-[?]->x-->b RETURN b

Requête
START a=node(4), x=node(3)
MATCH p = shortestPath( a-[?*]->x )
RETURN p

Essayez cette requête en live(1) {"name":"Dilshad"} (2) {"name":"Emil"} (3) {"name":"Filipa"} (4) {"name":"Anders"} (5) {"name":"Becky"} (6) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (4)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(6) {} (4)-[:KNOWS]->(1) {} (5)-[:KNOWS]->(2) {} (6)-[:KNOWS]->(2) {} START a=node(4), x=node(3) MATCH p = shortestPath( a-[?*]->x ) RETURN p

Contrôler la profondeur

Un pattern de relation peut s'étendre à plusieurs relations du graphe. Celles-ci sont appelées des relations à longueur variable, et sont marquées comme ceci en utilisant un astérisque (*):

(a)-[*]->(b)

Ceci signifie un chemin partant du noeud du pattern a, suivant seulement les relations sortant, jusqu'à ce qu’il atteigne un noeud du pattern b. Toutes les relations peuvent être suivies en cherchant un chemin vers b, ce qui peut résulter en une requête très gourmande dépendant de la nature de votre graphe.

Vous pouvez spécifier le nombre minimum d'étapes qui doivent être contenues et/ou un nombre maximum d'étapes:

(a)-[*3..5]->(b)

Ceci est une relation à longueur variable contenant au moins trois relations dans le graphe et au maximum cinq.

Les relations à longueur variable ne peuvent pas être utilisées avec CREATE et CREATE UNIQUE.

Comme simple exemple, prenons la requête ci-dessous:

Requête
START me=node(3)
MATCH me-[:KNOWS*1..2]-remote_friend
RETURN remote_friend
Résultat
remote_friend
0 row
0 ms

(empty result)

Essayez cette requête en live(1) {"name":"Dilshad"} (2) {"name":"Emil"} (3) {"name":"Filipa"} (4) {"name":"Anders"} (5) {"name":"Becky"} (6) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (4)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(6) {} (4)-[:KNOWS]->(1) {} (5)-[:KNOWS]->(2) {} (6)-[:KNOWS]->(2) {} START me=node(3) MATCH me-[:KNOWS*1..2]-remote_friend RETURN remote_friend

Cette requête part d’un noeud et suit les relations KNOWS deux ou trois pas plus loin et ensuite s’arrête.

Assigner un chemin à un identifiant de chemin

Dans une base de données graphe, un chemin est un concept important. Un chemin est une collection de noeuds et de relations qui décrivent un chemin dans le graphe. Afin d’assigner un chemin à un identifant de chemin, vous assignez simplement un pattern de chemin à un identifiant, comme ceci:

p = (a)-[*3..5]->(b)

Vous pouvez le faire avec MATCH, CREATE et CREATE UNIQUE, mais pas quand vous utilisez des patterns comme expressions. Un exemple des trois en une seule requête:

Requête
START me=node(3)
MATCH p1 = me-[*2]-friendOfFriend
CREATE p2 = me-[:MARRIED_TO]-(wife {name:"Gunhild"})
CREATE UNIQUE p3 = wife-[:KNOWS]-friendOfFriend
RETURN p1,p2,p3

Essayez cette requête en live(1) {"name":"Dilshad"} (2) {"name":"Emil"} (3) {"name":"Filipa"} (4) {"name":"Anders"} (5) {"name":"Becky"} (6) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (4)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(6) {} (4)-[:KNOWS]->(1) {} (5)-[:KNOWS]->(2) {} (6)-[:KNOWS]->(2) {} START me=node(3) MATCH p1 = me-[*2]-friendOfFriend CREATE p2 = me-[:MARRIED_TO]-(wife {name:"Gunhild"}) CREATE UNIQUE p3 = wife-[:KNOWS]-friendOfFriend RETURN p1,p2,p3

Ajouter des propriétés

Les noeuds et relations sont importants, mais Neo4j utilise des propriétés sur les deux afin d’avoir des modèles graphes à grande densité.

Les propriétés sont exprimées dans les patterns en utilisant une map de construction, qui est simplement des accolades entourant un nombre de paires clé-valeur séparées par une virgule, par ex. { name: "Andres", sport: "BJJ" }. Si la map est fournie en tant que paramètre, l’expression normale de paramètre est utilisée: { paramName }.

Les maps sont seulement utilisées par CREATE et CREATE UNIQUE. Avec CREATE ils sont utilisés pour assigner les propriétés aux noeuds et relations nouvellement créés.

Quand ils sont utilisés avec CREATE UNIQUE, ils sont utilisés afin de faire correspondre un élément de pattern avec un élément de graphe. La correspondance est correcte si les propriétés de l'élément de pattern correspondent exactement aux propriétés d’un élément de graphe. L'élément de graphe peut avoir des propriétés additionnelles et ils n’affectent pas la correspondance. Si Neo4j échoue à trouve des éléments de graphe correspondants, les maps sont utilisés pour affecter les valeurs aux nouveaux éléments créés.

Viewing Execution Plans

Cypher works very hard to execute queries as fast as possible. However, when optimizing for maximum query execution performance, it may be helpful to rephrase queries using knowledge about the domain and the application.

The overall goal of manual query performance optimization is to ensure that only necessary data is retrieved from the graph or at least that data gets filtered out as early as possible in order to reduce the amount of work that has to be done at later stages of query execution.

In order to be able to do this manual optimization, it is necessary to understand how a given query is going to be executed.

Each Cypher query gets transformed into an execution plan by the Cypher execution engine. The execution plan consists of a series of execution steps called pipes which may produce data, transform it, or even update the database or the schema. Data enters the execution at one pipe and is passed on from pipe to pipe until it either is filtered out, transformed, or returned to the user.

To look at the execution plan description used to satisfy your query, you can look at the execution plan description on the execution result object returned by the Java Core API.

Start

Chaque requête décrit un pattern, et dans ce pattern il peut il y avoir plusieurs points de départ. Un point de départ est une relation ou un noeud où un pattern est ancré. Vous pouvez introduire des points de départ comme id ou par lookup d’index. Notez qu’essayer d’utiliser un index qui n’existe pas déclenchera une exception.

cypher-start-graph.svg
Figure 12. Graph

Noeud par id

Binding a node as a starting point is done with the node(*) function.

Note
Neo4j reuses its internal ids when nodes and relationships are deleted, which means it’s bad practice to refer to them this way. Instead, use application generated ids.
Requête
START n=node(1)
RETURN n

Le noeud correspondant est retourné.

Résultat
n
1 row
0 ms

Node[1]{name:"A"}

Essayez cette requête en live(1) {"name":"A"} (2) {"name":"B"} (3) {"name":"C"} (1)-[:KNOWS]->(2) {} (1)-[:KNOWS]->(3) {} start n=node(1) return n

Relation par id

Binding a relationship as a starting point is done with the relationship(*) function, which can also be abbreviated rel(*). See [start-node-by-id] for more information on Neo4j ids.

Requête
START r=relationship(0)
RETURN r

La relation avec id 0 est retournée.

Résultat
r
1 row
0 ms

:KNOWS[0] {}

Essayez cette requête en live(1) {"name":"A"} (2) {"name":"B"} (3) {"name":"C"} (1)-[:KNOWS]->(2) {} (1)-[:KNOWS]->(3) {} start r=relationship(0) return r

Noeuds multiples par ids

Les noeuds multiples sont sélectionnés en les listant et les séparant avec une virgule.

Requête
START n=node(1, 2, 3)
RETURN n

Ceci retourne les noeuds listés dans le déclaratif START

Résultat
n
3 rows
0 ms

Node[1]{name:"A"}

Node[2]{name:"B"}

Node[3]{name:"C"}

Essayez cette requête en live(1) {"name":"A"} (2) {"name":"B"} (3) {"name":"C"} (1)-[:KNOWS]->(2) {} (1)-[:KNOWS]->(3) {} start n=node(1, 2, 3) return n

Tous les noeuds

Pour retourner tous les noeuds, utilisez un astérisque. Ceci peut être utilisé également sur les relations.

Requête
START n=node(*)
RETURN n

Cette requête retourne tous les noeuds du graphe.

Résultat
n
3 rows
0 ms

Node[1]{name:"A"}

Node[2]{name:"B"}

Node[3]{name:"C"}

Essayez cette requête en live(1) {"name":"A"} (2) {"name":"B"} (3) {"name":"C"} (1)-[:KNOWS]->(2) {} (1)-[:KNOWS]->(3) {} start n=node(*) return n

Noeud par lookup d’index

Quand le point de départ peut être retrouvé par un lookup d’index, cela se fait ainsi: node:index-name(key = "value"). Dans cet exemple, un index nommé nodes existe.

Requête
START n=node:nodes(name = "A")
RETURN n

La requête retourne le noeud indexé avec le nom "A".

Résultat
n
1 row
0 ms

Node[1]{name:"A"}

Relationship by index lookup

When the starting point can be found by using index lookups, it can be done like this: relationship:index-name(key = "value").

Query
START r=relationship:rels(name = "Andrés")
RETURN r

The relationship indexed with the name property set to "Andrés" is returned by the query.

Result
r
1 row
0 ms

:KNOWS[0] {name:"Andrés"

Noeud par requête d’index

Quand le point de départ peut être trouvé par plusieurs requêtes Lucene complexes, la syntaxe à utiliser est la suivante : node:index-name("query"). Cela vous permet d'écrire des requêtes d’index plus avancées.

Requête
START n=node:nodes("name:A")
RETURN n

The node indexed with name "A" is returned by the query.

Résultat
n
1 row
1 ms

Node[1]{name:"A"}

Multiple starting points

Sometimes you want to bind multiple starting points. Just list them separated by commas.

Requête
START a=node(1), b=node(2)
RETURN a,b

Both the nodes A and the B are returned.

Résultat
ab
1 row
0 ms

Node[1]{name:"A"}

Node[2]{name:"B"}

Essayez cette requête en live(1) {"name":"A"} (2) {"name":"B"} (3) {"name":"C"} (1)-[:KNOWS]->(2) {} (1)-[:KNOWS]->(3) {} start a=node(1), b=node(2) return a,b

Match

Introduction

Astuce
Dans la clause MATCH, les patterns sont beaucoup utilisés. Lisez [introduction-pattern] pour une introduction.

Le graphe suivant est utilisé pour les exemples:

cypher-match-graph.svg
Figure 13. Graph

The symbol -- means related to, without regard to type or direction.

Query
START n=node(3)
MATCH (n)--(x)
RETURN x

All nodes related to A (Anders) are returned by the query.

Result
x
3 rows
0 ms

Node[4]{name:"Bossman"}

Node[1]{name:"David"}

Node[5]{name:"Cesar"}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start n=node(3) match (n)--(x) return x

Outgoing relationships

When the direction of a relationship is interesting, it is shown by using --> or <--, like this:

Query
START n=node(3)
MATCH (n)-->(x)
RETURN x

All nodes that A has outgoing relationships to are returned.

Result
x
2 rows
1 ms

Node[4]{name:"Bossman"}

Node[5]{name:"Cesar"}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start n=node(3) match (n)-->(x) return x

Directed relationships and identifier

If an identifier is needed, either for filtering on properties of the relationship, or to return the relationship, this is how you introduce the identifier.

Query
START n=node(3)
MATCH (n)-[r]->()
RETURN r

The query returns all outgoing relationships from node A.

Result
r
2 rows
1 ms

:KNOWS[0] {}

:BLOCKS[1] {}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start n=node(3) match (n)-[r]->() return r

Match by relationship type

When you know the relationship type you want to match on, you can specify it by using a colon together with the relationship type.

Query
START n=node(3)
MATCH (n)-[:BLOCKS]->(x)
RETURN x

All nodes that are BLOCKed by A are returned by this query.

Result
x
1 row
0 ms

Node[5]{name:"Cesar"}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start n=node(3) match (n)-[:BLOCKS]->(x) return x

Match by multiple relationship types

To match on one of multiple types, you can specify this by chaining them together with the pipe symbol |.

Query
START n=node(3)
MATCH (n)-[:BLOCKS|KNOWS]->(x)
RETURN x

All nodes with a BLOCK or KNOWS relationship to A are returned.

Result
x
2 rows
0 ms

Node[5]{name:"Cesar"}

Node[4]{name:"Bossman"}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start n=node(3) match (n)-[:BLOCKS|KNOWS]->(x) return x

Match by relationship type and use an identifier

If you both want to introduce an identifier to hold the relationship, and specify the relationship type you want, just add them both, like this.

Query
START n=node(3)
MATCH (n)-[r:BLOCKS]->()
RETURN r

All BLOCKS relationships going out from A are returned.

Result
r
1 row
0 ms

:BLOCKS[1] {}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start n=node(3) match (n)-[r:BLOCKS]->() return r

Relationship types with uncommon characters

Sometime your database will have types with non-letter characters, or with spaces in them. Use ` (backtick) to quote these.

Query
START n=node(3)
MATCH (n)-[r:`TYPE THAT HAS SPACE IN IT`]->()
RETURN r

This query returns a relationship of a type with spaces in it.

Result
r
1 row
0 ms

:TYPE THAT HAS SPACE IN IT[6] {}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (3)-[:TYPE THAT HAS SPACE IN IT]->(3) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start n=node(3) match (n)-[r:`TYPE THAT HAS SPACE IN IT`]->() return r

Multiple relationships

Relationships can be expressed by using multiple statements in the form of ()--(), or they can be strung together, like this:

Query
START a=node(3)
MATCH (a)-[:KNOWS]->(b)-[:KNOWS]->(c)
RETURN a,b,c

The three nodes in the path are returned by the query.

Result
abc
1 row
0 ms

Node[3]{name:"Anders"}

Node[4]{name:"Bossman"}

Node[2]{name:"Emil"}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start a=node(3) match (a)-[:KNOWS]->(b)-[:KNOWS]->(c) return a,b,c

Variable length relationships

Nodes that are a variable number of relationship→node hops away can be found using the following syntax: -[:TYPE*minHops..maxHops]->. minHops and maxHops are optional and default to 1 and infinity respectively. When no bounds are given the dots may be omitted.

Query
START a=node(3), x=node(2, 4)
MATCH a-[:KNOWS*1..3]->x
RETURN a,x

This query returns the start and end point, if there is a path between 1 and 3 relationships away.

Result
ax
2 rows
1 ms

Node[3]{name:"Anders"}

Node[2]{name:"Emil"}

Node[3]{name:"Anders"}

Node[4]{name:"Bossman"}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start a=node(3), x=node(2, 4) match a-[:KNOWS*1..3]->x return a,x

Relationship identifier in variable length relationships

When the connection between two nodes is of variable length, a relationship identifier becomes an collection of relationships.

Query
START a=node(3), x=node(2, 4)
MATCH a-[r:KNOWS*1..3]->x
RETURN r

The query returns the relationships, if there is a path between 1 and 3 relationships away.

Result
r
2 rows
1 ms

[:KNOWS[0] {},:KNOWS[3] {}]

[:KNOWS[0] {}]

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start a=node(3), x=node(2, 4) match a-[r:KNOWS*1..3]->x return r

Zero length paths

Using variable length paths that have the lower bound zero means that two identifiers can point to the same node. If the distance between two nodes is zero, they are by definition the same node. Note that when matching zero length paths the result may contain a match even when matching on a relationship type not in use.

Query
START a=node(3)
MATCH p1=a-[:KNOWS*0..1]->b, p2=b-[:BLOCKS*0..1]->c
RETURN a,b,c, length(p1), length(p2)

This query will return four paths, some of which have length zero.

Result
abclength(p1)length(p2)
4 rows
2 ms

Node[3]{name:"Anders"}

Node[3]{name:"Anders"}

Node[3]{name:"Anders"}

0

0

Node[3]{name:"Anders"}

Node[3]{name:"Anders"}

Node[5]{name:"Cesar"}

0

1

Node[3]{name:"Anders"}

Node[4]{name:"Bossman"}

Node[4]{name:"Bossman"}

1

0

Node[3]{name:"Anders"}

Node[4]{name:"Bossman"}

Node[1]{name:"David"}

1

1

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start a=node(3) match p1=a-[:KNOWS*0..1]->b, p2=b-[:BLOCKS*0..1]->c return a,b,c, length(p1), length(p2)

Optional relationship

If a relationship is optional, it can be marked with a question mark. This is similar to how a SQL outer join works. If the relationship is there, it is returned. If it’s not, null is returned in it’s place. Remember that anything hanging off an optional relationship, is in turn optional, unless it is connected with a bound node through some other path.

Query
START a=node(2)
MATCH a-[?]->x
RETURN a,x

A node, and null are returned, since the node has no outgoing relationships.

Result
ax
1 row
1 ms

Node[2]{name:"Emil"}

<null>

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start a=node(2) match a-[?]->x return a,x

Optional typed and named relationship

Just as with a normal relationship, you can decide which identifier it goes into, and what relationship type you need.

Query
START a=node(3)
MATCH a-[r?:LOVES]->()
RETURN a,r

This returns a node, and null, since the node has no outgoing LOVES relationships.

Result
ar
1 row
1 ms

Node[3]{name:"Anders"}

<null>

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start a=node(3) match a-[r?:LOVES]->() return a,r

Properties on optional elements

Returning a property from an optional element that is null will also return null.

Query
START a=node(2)
MATCH a-[?]->x
RETURN x, x.name

This returns the element x (null in this query), and null as it’s name.

Result
xx.name
1 row
1 ms

<null>

<null>

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start a=node(2) match a-[?]->x return x, x.name

Complex matching

Using Cypher, you can also express more complex patterns to match on, like a diamond shape pattern.

Query
START a=node(3)
MATCH (a)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:BLOCKS]-(d)-[:KNOWS]-(c)
RETURN a,b,c,d

This returns the four nodes in the paths.

Result
abcd
1 row
1 ms

Node[3]{name:"Anders"}

Node[4]{name:"Bossman"}

Node[2]{name:"Emil"}

Node[5]{name:"Cesar"}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start a=node(3) match (a)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:BLOCKS]-(d)-[:KNOWS]-(c) return a,b,c,d

Shortest path

Finding a single shortest path between two nodes is as easy as using the shortestPath function. It’s done like this:

Query
START d=node(1), e=node(2)
MATCH p = shortestPath( d-[*..15]->e )
RETURN p

This means: find a single shortest path between two nodes, as long as the path is max 15 relationships long. Inside of the parenthesis you define a single link of a path — the starting node, the connecting relationship and the end node. Characteristics describing the relationship like relationship type, max hops and direction are all used when finding the shortest path. You can also mark the path as optional.

Result
p
1 row
0 ms

[Node[1]{name:"David"},:KNOWS[2] {},Node[3]{name:"Anders"},:KNOWS[0] {},Node[4]{name:"Bossman"},:KNOWS[3] {},Node[2]{name:"Emil"}]

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start d=node(1), e=node(2) match p = shortestPath( d-[*..15]->e ) return p

All shortest paths

Finds all the shortest paths between two nodes.

Query
START d=node(1), e=node(2)
MATCH p = allShortestPaths( d-[*..15]->e )
RETURN p

This example will find the two directed paths between David and Emil.

Result
p
2 rows
1 ms

[Node[1]{name:"David"},:KNOWS[2] {},Node[3]{name:"Anders"},:KNOWS[0] {},Node[4]{name:"Bossman"},:KNOWS[3] {},Node[2]{name:"Emil"}]

[Node[1]{name:"David"},:KNOWS[2] {},Node[3]{name:"Anders"},:BLOCKS[1] {},Node[5]{name:"Cesar"},:KNOWS[4] {},Node[2]{name:"Emil"}]

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start d=node(1), e=node(2) match p = allShortestPaths( d-[*..15]->e ) return p

Named path

If you want to return or filter on a path in your pattern graph, you can a introduce a named path.

Query
START a=node(3)
MATCH p = a-->b
RETURN p

This returns the two paths starting from the first node.

Result
p
2 rows
0 ms

[Node[3]{name:"Anders"},:KNOWS[0] {},Node[4]{name:"Bossman"}]

[Node[3]{name:"Anders"},:BLOCKS[1] {},Node[5]{name:"Cesar"}]

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start a=node(3) match p = a-->b return p

Matching on a bound relationship

When your pattern contains a bound relationship, and that relationship pattern doesn’t specify direction, Cypher will try to match the relationship where the connected nodes switch sides.

Query
START r=rel(0)
MATCH a-[r]-b
RETURN a,b

This returns the two connected nodes, once as the start node, and once as the end node.

Result
ab
2 rows
1 ms

Node[3]{name:"Anders"}

Node[4]{name:"Bossman"}

Node[4]{name:"Bossman"}

Node[3]{name:"Anders"}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start r=rel(0) match a-[r]-b return a,b

Match with OR

Strictly speaking, you can’t do OR in your MATCH. It’s still possible to form a query that works a lot like OR.

Query
START a=node(3), b=node(2)
MATCH a-[?:KNOWS]-x-[?:KNOWS]-b
RETURN x

This query is saying: give me the nodes that are connected to a, or b, or both.

Result
x
3 rows
3 ms

Node[4]{name:"Bossman"}

Node[5]{name:"Cesar"}

Node[1]{name:"David"}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start a=node(3), b=node(2) match a-[?:KNOWS]-x-[?:KNOWS]-b return x

Where

Si vous nécessitez un filtrage à part du pattern de données que vous cherchez, vous pouvez ajouter cela à l’aide de clauses dans la partie WHERE de la requête.

cypher-where-graph.svg
Figure 14. Graph

Boolean operations

You can use the expected boolean operators AND and OR, and also the boolean function NOT().

Query
START n=node(3, 1)
WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias")
RETURN n

This will return both nodes in the start clause.

Result
n
2 rows
0 ms

Node[3]{name:"Andres",age:36,belt:"white"}

Node[1]{name:"Tobias",age:25}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 1) where (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") return n

Filter on node property

To filter on a property, write your clause after the WHERE keyword. Filtering on relationship properties works just the same way.

Query
START n=node(3, 1)
WHERE n.age < 30
RETURN n

The "Tobias" node will be returned.

Result
n
1 row
0 ms

Node[1]{name:"Tobias",age:25}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 1) where n.age < 30 return n

Regular expressions

You can match on regular expressions by using =~ "regexp", like this:

Query
START n=node(3, 1)
WHERE n.name =~ 'Tob.*'
RETURN n

The "Tobias" node will be returned.

Result
n
1 row
0 ms

Node[1]{name:"Tobias",age:25}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 1) where n.name =~ 'Tob.*' return n

Escaping in regular expressions

If you need a forward slash inside of your regular expression, escape it. Remember that back slash needs to be escaped in string literals

Query
START n=node(3, 1)
WHERE n.name =~ 'Some\\/thing'
RETURN n

No nodes match this regular expression.

Result
n
0 row
0 ms

(empty result)

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 1) where n.name =~ 'Some\\/thing' return n

Case insensitive regular expressions

By pre-pending a regular expression with (?i), the whole expression becomes case insensitive.

Query
START n=node(3, 1)
WHERE n.name =~ '(?i)ANDR.*'
RETURN n

The node with name "Andres" is returned.

Result
n
1 row
0 ms

Node[3]{name:"Andres",age:36,belt:"white"}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 1) where n.name =~ '(?i)ANDR.*' return n

Filtering on relationship type

You can put the exact relationship type in the MATCH pattern, but sometimes you want to be able to do more advanced filtering on the type. You can use the special property TYPE to compare the type with something else. In this example, the query does a regular expression comparison with the name of the relationship type.

Query
START n=node(3)
MATCH (n)-[r]->()
WHERE type(r) =~ 'K.*'
RETURN r

This returns relationships that has a type whose name starts with K.

Result
r
2 rows
1 ms

:KNOWS[0] {}

:KNOWS[1] {}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3) match (n)-[r]->() where type(r) =~ 'K.*' return r

Property exists

To only include nodes/relationships that have a property, use the HAS() function and just write out the identifier and the property you expect it to have.

Query
START n=node(3, 1)
WHERE has(n.belt)
RETURN n

The node named "Andres" is returned.

Result
n
1 row
0 ms

Node[3]{name:"Andres",age:36,belt:"white"}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 1) where has(n.belt) return n

Default true if property is missing

If you want to compare a property on a graph element, but only if it exists, use the nullable property syntax. You can use a question mark if you want missing property to return true, like:

Query
START n=node(3, 1)
WHERE n.belt? = 'white'
RETURN n

This returns all nodes, even those without the belt property.

Result
n
2 rows
0 ms

Node[3]{name:"Andres",age:36,belt:"white"}

Node[1]{name:"Tobias",age:25}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 1) where n.belt? = 'white' return n

Default false if property is missing

When you need missing property to evaluate to false, use the exclamation mark.

Query
START n=node(3, 1)
WHERE n.belt! = 'white'
RETURN n

No nodes without the belt property are returned.

Result
n
1 row
0 ms

Node[3]{name:"Andres",age:36,belt:"white"}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 1) where n.belt! = 'white' return n

Filter on null values

Sometimes you might want to test if a value or an identifier is null. This is done just like SQL does it, with IS NULL. Also like SQL, the negative is IS NOT NULL, although NOT(IS NULL x) also works.

Query
START a=node(1), b=node(3, 2)
MATCH a<-[r?]-b
WHERE r is null
RETURN b

Nodes that Tobias is not connected to are returned.

Result
b
1 row
1 ms

Node[2]{name:"Peter",age:34}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start a=node(1), b=node(3, 2) match a<-[r?]-b where r is null return b

Filter on patterns

Patterns are expressions in Cypher, expressions that return a collection of paths. Collection expressions are also predicates — an empty collection represents false, and a non-empty represents true.

So, patterns are not only expressions, they are also predicates. The only limitation to your pattern is that you must be able to express it in a single path. You can not use commas between multiple paths like you do in MATCH. You can achieve the same effect by combining multiple patterns with AND.

Note that you can not introduce new identifiers here. Although it might look very similar to the MATCH patterns, the WHERE clause is all about eliminating matched subgraphs. MATCH a-[*]->b is very different from WHERE a-[*]->b; the first will produce a subgraph for every path it can find between a and b, and the latter will eliminate any matched subgraphs where a and b do not have a directed relationship chain between them.

Query
START tobias=node(1), others=node(3, 2)
WHERE tobias<--others
RETURN others

Nodes that have an outgoing relationship to the "Tobias" node are returned.

Result
others
1 row
0 ms

Node[3]{name:"Andres",age:36,belt:"white"}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start tobias=node(1), others=node(3, 2) where tobias<--others return others

Filter on patterns using NOT

The NOT() function can be used to exclude a pattern.

Query
START persons=node(*), peter=node(2)
WHERE not(persons-->peter)
RETURN persons

Nodes that do not have an outgoing relationship to the "Peter" node are returned.

Result
persons
2 rows
0 ms

Node[1]{name:"Tobias",age:25}

Node[2]{name:"Peter",age:34}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start persons=node(*), peter=node(2) where not(persons-->peter) return persons

IN operator

To check if an element exists in a collection, you can use the IN operator.

Query
START a=node(3, 1, 2)
WHERE a.name IN ["Peter", "Tobias"]
RETURN a

This query shows how to check if a property exists in a literal collection.

Result
a
2 rows
0 ms

Node[1]{name:"Tobias",age:25}

Node[2]{name:"Peter",age:34}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"belt":"white","name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start a=node(3, 1, 2) where a.name IN ["Peter", "Tobias"] return a

Return

Dans la partie RETURN de votre requête, vous définissez quelles parties du pattern vous intéressent. Il peut s’agir de noeuds, relations ou propriétés sur ceux-ci.

cypher-return-graph.svg
Figure 15. Graph

Return nodes

To return a node, list it in the RETURN statemenet.

Query
START n=node(2)
RETURN n

The example will return the node.

Result
n
1 row
0 ms

Node[2]{name:"B"}

Try this query live(1) {"age":55,"happy":"Yes!","name":"A"} (2) {"name":"B"} (1)-[:KNOWS]->(2) {} (1)-[:BLOCKS]->(2) {} start n=node(2) return n

Return relationships

To return a relationship, just include it in the RETURN list.

Query
START n=node(1)
MATCH (n)-[r:KNOWS]->(c)
RETURN r

The relationship is returned by the example.

Result
r
1 row
0 ms

:KNOWS[0] {}

Try this query live(1) {"age":55,"happy":"Yes!","name":"A"} (2) {"name":"B"} (1)-[:KNOWS]->(2) {} (1)-[:BLOCKS]->(2) {} start n=node(1) match (n)-[r:KNOWS]->(c) return r

Return property

To return a property, use the dot separator, like this:

Query
START n=node(1)
RETURN n.name

The value of the property name gets returned.

Result
n.name
1 row
0 ms

"A"

Try this query live(1) {"age":55,"happy":"Yes!","name":"A"} (2) {"name":"B"} (1)-[:KNOWS]->(2) {} (1)-[:BLOCKS]->(2) {} start n=node(1) return n.name

Return all elements

When you want to return all nodes, relationships and paths found in a query, you can use the * symbol.

Query
START a=node(1)
MATCH p=a-[r]->b
RETURN *

This returns the two nodes, the relationship and the path used in the query.

Result
barp
2 rows
0 ms

Node[2]{name:"B"}

Node[1]{name:"A",happy:"Yes!",age:55}

:KNOWS[0] {}

[Node[1]{name:"A",happy:"Yes!",age:55},:KNOWS[0] {},Node[2]{name:"B"}]

Node[2]{name:"B"}

Node[1]{name:"A",happy:"Yes!",age:55}

:BLOCKS[1] {}

[Node[1]{name:"A",happy:"Yes!",age:55},:BLOCKS[1] {},Node[2]{name:"B"}]

Try this query live(1) {"age":55,"happy":"Yes!","name":"A"} (2) {"name":"B"} (1)-[:KNOWS]->(2) {} (1)-[:BLOCKS]->(2) {} start a=node(1) match p=a-[r]->b return *

Identifier with uncommon characters

To introduce a placeholder that is made up of characters that are outside of the english alphabet, you can use the ` to enclose the identifier, like this:

Query
START `This isn't a common identifier`=node(1)
RETURN `This isn't a common identifier`.happy

The node indexed with name "A" is returned

Result
This isn't a common identifier.happy
1 row
0 ms

"Yes!"

Try this query live(1) {"age":55,"happy":"Yes!","name":"A"} (2) {"name":"B"} (1)-[:KNOWS]->(2) {} (1)-[:BLOCKS]->(2) {} start `This isn't a common identifier`=node(1) return `This isn't a common identifier`.happy

Column alias

If the name of the column should be different from the expression used, you can rename it by using AS <new name>.

Query
START a=node(1)
RETURN a.age AS SomethingTotallyDifferent

Returns the age property of a node, but renames the column.

Result
SomethingTotallyDifferent
1 row
0 ms

55

Try this query live(1) {"age":55,"happy":"Yes!","name":"A"} (2) {"name":"B"} (1)-[:KNOWS]->(2) {} (1)-[:BLOCKS]->(2) {} start a=node(1) return a.age AS SomethingTotallyDifferent

Optional properties

If a property might or might not be there, you can select it optionally by adding a questionmark to the identifier, like this:

Query
START n=node(1, 2)
RETURN n.age?

This example returns the age when the node has that property, or null if the property is not there.

Result
n.age?
2 rows
0 ms

55

<null>

Try this query live(1) {"age":55,"happy":"Yes!","name":"A"} (2) {"name":"B"} (1)-[:KNOWS]->(2) {} (1)-[:BLOCKS]->(2) {} start n=node(1, 2) return n.age?

Other expressions

Any expression can be used as a return iterm - literals, predicates, properties, functions, and everything else.

Query
START a=node(1)
RETURN a.age > 30, "I'm a literal", length(a-->())

Returns a predicate, a literal and function call with a pattern expression parameter.

Result
a.age > 30"I'm a literal"length(a-->())
1 row
1 ms

true

"I'm a literal"

2

Try this query live(1) {"age":55,"happy":"Yes!","name":"A"} (2) {"name":"B"} (1)-[:KNOWS]->(2) {} (1)-[:BLOCKS]->(2) {} start a=node(1) return a.age > 30, "I'm a literal", length(a-->())

Unique results

DISTINCT retrieves only unique rows depending on the columns that have been selected to output.

Query
START a=node(1)
MATCH (a)-->(b)
RETURN distinct b

The node named B is returned by the query, but only once.

Result
b
1 row
0 ms

Node[2]{name:"B"}

Try this query live(1) {"age":55,"happy":"Yes!","name":"A"} (2) {"name":"B"} (1)-[:KNOWS]->(2) {} (1)-[:BLOCKS]->(2) {} start a=node(1) match (a)-->(b) return distinct b

Agrégation

Introduction

To calculate aggregated data, Cypher offers aggregation, much like SQL’s GROUP BY.

Aggregate functions take multiple input values and calculate an aggregated value from them. Examples are AVG that calculate the average of multiple numeric values, or MIN that finds the smallest numeric value in a set of values.

Aggregation can be done over all the matching sub graphs, or it can be further divided by introducing key values. These are non-aggregate expressions, that are used to group the values going into the aggregate functions.

So, if the return statement looks something like this:

RETURN n, count(*)

We have two return expressions — n, and count(*). The first, n, is no aggregate function, and so it will be the grouping key. The latter, count(*) is an aggregate expression. So the matching subgraphs will be divided into different buckets, depending on the grouping key. The aggregate function will then run on these buckets, calculating the aggregate values.

If you want to use aggregations to sort your result set, the aggregation must be included in the RETURN to be used in your ORDER BY.

The last piece of the puzzle is the DISTINCT keyword. It is used to make all values unique before running them through an aggregate function.

An example might be helpful:

Query
START me=node(1)
MATCH me-->friend-->friend_of_friend
RETURN count(distinct friend_of_friend), count(friend_of_friend)

In this example we are trying to find all our friends of friends, and count them. The first aggregate function, count(distinct friend_of_friend), will only see a friend_of_friend once — DISTINCT removes the duplicates. The latter aggregate function, count(friend_of_friend), might very well see the same friend_of_friend multiple times. Since there is no real data in this case, an empty result is returned. See the sections below for real data.

Result
count(distinct friend_of_friend)count(friend_of_friend)
1 row
0 ms

0

0

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} START me=node(1) MATCH me-->friend-->friend_of_friend RETURN count(distinct friend_of_friend), count(friend_of_friend)

Les exemples suivants prennent comme base le graphe ci-dessous.

cypher-aggregation-graph.svg
Figure 16. Graph

COUNT

COUNT est utilisé pour compter le nombre de lignes. COUNT peut être utilisé de deux façons différentes-- ‘COUNT(*)` qui compte seulement le nombre de lignes correspondantes et COUNT(<identifiant>) qui compte le nombre d’entrées qui ont une valeur non-nulle pour l’+<identifiant>+.

Count nodes

To count the number of nodes, for example the number of nodes connected to one node, you can use count(*).

Query
START n=node(2)
MATCH (n)-->(x)
RETURN n, count(*)

This returns the start node and the count of related nodes.

Result
ncount(*)
1 row
0 ms

Node[2]{name:"A",property:13}

3

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2) match (n)-->(x) return n, count(*)

Group Count Relationship Types

To count the groups of relationship types, return the types and count them with count(*).

Query
START n=node(2)
MATCH (n)-[r]->()
RETURN type(r), count(*)

The relationship types and their group count is returned by the query.

Result
type(r)count(*)
1 row
0 ms

"KNOWS"

3

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2) match (n)-[r]->() return type(r), count(*)

Count entities

Instead of counting the number of results with count(*), it might be more expressive to include the name of the identifier you care about.

Query
START n=node(2)
MATCH (n)-->(x)
RETURN count(x)

The example query returns the number of connected nodes from the start node.

Result
count(x)
1 row
0 ms

3

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2) match (n)-->(x) return count(x)

Count non-null values

You can count the non-null values by using count(<identifier>).

Query
START n=node(2,3,4,1)
RETURN count(n.property?)

The count of related nodes with the property property set is returned by the query.

Result
count(n.property?)
1 row
0 ms

3

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2,3,4,1) return count(n.property?)

SUM

The SUM aggregation function simply sums all the numeric values it encounters. Nulls are silently dropped. This is an example of how you can use SUM.

Query
START n=node(2,3,4)
RETURN sum(n.property)

This returns the sum of all the values in the property property.

Result
sum(n.property)
1 row
0 ms

90

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2,3,4) return sum(n.property)

AVG

AVG calculates the average of a numeric column.

Query
START n=node(2,3,4)
RETURN avg(n.property)

The average of all the values in the property property is returned by the example query.

Result
avg(n.property)
1 row
0 ms

30.0

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2,3,4) return avg(n.property)

PERCENTILE_DISC

PERCENTILE_DISC calculates the percentile of a given value over a group, with a percentile from 0.0 to 1.0. It uses a rounding method, returning the nearest value to the percentile. For interpolated values, see PERCENTILE_CONT.

Query
START n=node(2,3,4)
RETURN percentile_disc(n.property, 0.5)

The 50th percentile of the values in the property property is returned by the example query. In this case, 0.5 is the median, or 50th percentile.

Result
percentile_disc(n.property, 0.5)
1 row
0 ms

33

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2,3,4) return percentile_disc(n.property, 0.5)

PERCENTILE_CONT

PERCENTILE_CONT calculates the percentile of a given value over a group, with a percentile from 0.0 to 1.0. It uses a linear interpolation method, calculating a weighted average between two values, if the desired percentile lies between them. For nearest values using a rounding method, see PERCENTILE_DISC.

Query
START n=node(2,3,4)
RETURN percentile_cont(n.property, 0.4)

The 40th percentile of the values in the property property is returned by the example query, calculated with a weighted average.

Result
percentile_cont(n.property, 0.4)
1 row
0 ms

29.0

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2,3,4) return percentile_cont(n.property, 0.4)

MAX

MAX find the largets value in a numeric column.

Query
START n=node(2,3,4)
RETURN max(n.property)

The largest of all the values in the property property is returned.

Result
max(n.property)
1 row
0 ms

44

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2,3,4) return max(n.property)

MIN

MIN takes a numeric property as input, and returns the smallest value in that column.

Query
START n=node(2,3,4)
RETURN min(n.property)

This returns the smallest of all the values in the property property.

Result
min(n.property)
1 row
0 ms

13

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2,3,4) return min(n.property)

COLLECT

COLLECT collects all the values into a list. It will ignore null values,

Query
START n=node(2,3,4,1)
RETURN collect(n.property?)

Returns a single row, with all the values collected.

Result
collect(n.property?)
1 row
0 ms

[13,33,44]

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start n=node(2,3,4,1) return collect(n.property?)

DISTINCT

All aggregation functions also take the DISTINCT modifier, which removes duplicates from the values. So, to count the number of unique eye colors from nodes related to a, this query can be used:

Query
START a=node(2)
MATCH a-->b
RETURN count(distinct b.eyes)

Returns the number of eye colors.

Result
count(distinct b.eyes)
1 row
0 ms

2

Try this query live(1) {"eyes":"brown","name":"D"} (2) {"name":"A","property":13} (3) {"eyes":"blue","name":"B","property":33} (4) {"eyes":"blue","name":"C","property":44} (2)-[:KNOWS]->(3) {} (2)-[:KNOWS]->(4) {} (2)-[:KNOWS]->(1) {} start a=node(2) match a-->b return count(distinct b.eyes)

Order by

Pour trier la sortie, utilisez la clause ORDER BY. Notez que vous ne pouvez pas trier sur les noeuds et relations mais seulement sur les propriétés de ceux-ci.

cypher-orderby-graph.svg
Figure 17. Graph

Order nodes by property

ORDER BY is used to sort the output.

Query
START n=node(3,1,2)
RETURN n
ORDER BY n.name

The nodes are returned, sorted by their name.

Result
n
3 rows
0 ms

Node[1]{name:"A",age:34,length:170}

Node[2]{name:"B",age:34}

Node[3]{name:"C",age:32,length:185}

Try this query live(1) {"age":34,"length":170,"name":"A"} (2) {"age":34,"name":"B"} (3) {"age":32,"length":185,"name":"C"} (1)-[:KNOWS]->(2) {} (2)-[:KNOWS]->(3) {} start n=node(3,1,2) return n order by n.name

Order nodes by multiple properties

You can order by multiple properties by stating each identifier in the ORDER BY clause. Cypher will sort the result by the first identifier listed, and for equals values, go to the next property in the ORDER BY clause, and so on.

Query
START n=node(3,1,2)
RETURN n
ORDER BY n.age, n.name

This returns the nodes, sorted first by their age, and then by their name.

Result
n
3 rows
0 ms

Node[3]{name:"C",age:32,length:185}

Node[1]{name:"A",age:34,length:170}

Node[2]{name:"B",age:34}

Try this query live(1) {"age":34,"length":170,"name":"A"} (2) {"age":34,"name":"B"} (3) {"age":32,"length":185,"name":"C"} (1)-[:KNOWS]->(2) {} (2)-[:KNOWS]->(3) {} start n=node(3,1,2) return n order by n.age, n.name

Order nodes in descending order

By adding DESC[ENDING] after the identifier to sort on, the sort will be done in reverse order.

Query
START n=node(3,1,2)
RETURN n
ORDER BY n.name DESC

The example returns the nodes, sorted by their name reversely.

Result
n
3 rows
0 ms

Node[3]{name:"C",age:32,length:185}

Node[2]{name:"B",age:34}

Node[1]{name:"A",age:34,length:170}

Try this query live(1) {"age":34,"length":170,"name":"A"} (2) {"age":34,"name":"B"} (3) {"age":32,"length":185,"name":"C"} (1)-[:KNOWS]->(2) {} (2)-[:KNOWS]->(3) {} start n=node(3,1,2) return n order by n.name DESC

Ordering null

When sorting the result set, null will always come at the end of the result set for ascending sorting, and first when doing descending sort.

Query
START n=node(3,1,2)
RETURN n.length?, n
ORDER BY n.length?

The nodes are returned sorted by the length property, with a node without that property last.

Result
n.length?n
3 rows
0 ms

170

Node[1]{name:"A",age:34,length:170}

185

Node[3]{name:"C",age:32,length:185}

<null>

Node[2]{name:"B",age:34}

Try this query live(1) {"age":34,"length":170,"name":"A"} (2) {"age":34,"name":"B"} (3) {"age":32,"length":185,"name":"C"} (1)-[:KNOWS]->(2) {} (2)-[:KNOWS]->(3) {} start n=node(3,1,2) return n.length?, n order by n.length?

Limit

LIMIT vous offre la possibilité de ne retourner qu’une partie du résultat total.

cypher-limit-graph.svg
Figure 18. Graph

Return first part

To return a subset of the result, starting from the top, use this syntax:

Query
START n=node(3, 4, 5, 1, 2)
RETURN n
LIMIT 3

The top three items are returned by the example query.

Result
n
3 rows
0 ms

Node[3]{name:"A"}

Node[4]{name:"B"}

Node[5]{name:"C"}

Try this query live(1) {"name":"D"} (2) {"name":"E"} (3) {"name":"A"} (4) {"name":"B"} (5) {"name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 4, 5, 1, 2) return n limit 3

Skip

SKIP vous offre la possibilité de ne retourner qu’une partie du résultat total. En utilisant SKIP, le résultat sera tronqué par le haut. Veuillez noter qu’il n’y a pas de garanties sur l’ordre du résultat sauf si vous utilisez la clause ORDER BY.

cypher-skip-graph.svg
Figure 19. Graph

Skip first three

To return a subset of the result, starting from the fourth result, use the following syntax:

Query
START n=node(3, 4, 5, 1, 2)
RETURN n
ORDER BY n.name
SKIP 3

The first three nodes are skipped, and only the last two are returned in the result.

Result
n
2 rows
0 ms

Node[1]{name:"D"}

Node[2]{name:"E"}

Try this query live(1) {"name":"D"} (2) {"name":"E"} (3) {"name":"A"} (4) {"name":"B"} (5) {"name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 4, 5, 1, 2) return n order by n.name skip 3

Return middle two

To return a subset of the result, starting from somewhere in the middle, use this syntax:

Query
START n=node(3, 4, 5, 1, 2)
RETURN n
ORDER BY n.name
SKIP 1
LIMIT 2

Two nodes from the middle are returned.

Result
n
2 rows
0 ms

Node[4]{name:"B"}

Node[5]{name:"C"}

Try this query live(1) {"name":"D"} (2) {"name":"E"} (3) {"name":"A"} (4) {"name":"B"} (5) {"name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n=node(3, 4, 5, 1, 2) return n order by n.name skip 1 limit 2

With

La possibilité de chaîner des requêtes entre elles vous permet de créer des constructions puissantes. Dans Cypher, la clause WITH est utilisée pour faire passer le résultat d’une requête dans la suivante.

WITH est également utilisé pour séparer la lecture et la mise à jour du graphe. Chaque sous-requête d’une requête doit être ou lecture seule ou écriture seule.

cypher-with-graph.svg
Figure 20. Graph

Filter on aggregate function results

Aggregated results have to pass through a WITH clause to be able to filter on.

Query
START david=node(1)
MATCH david--otherPerson-->()
WITH otherPerson, count(*) as foaf
WHERE foaf > 1
RETURN otherPerson

The person connected to David with the at least more than one outgoing relationship will be returned by the query.

Result
otherPerson
1 row
0 ms

Node[3]{name:"Anders"}

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start david=node(1) match david--otherPerson-->() with otherPerson, count(*) as foaf where foaf > 1 return otherPerson

Sort results before using collect on them

You can sort your results before passing them to collect, thus sorting the resulting collection.

Query
START n=node(*)
WITH n
ORDER BY n.name desc
LIMIT 3
RETURN collect(n.name)

A list of the names of people in reverse order, limited to 3, in a collection.

Result
collect(n.name)
1 row
0 ms

["Emil","David","Cesar"]

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start n=node(*) with n order by n.name desc limit 3 return collect(n.name)

You can match paths, limit to a certain number, and then match again using those paths as a base As well as any number of similar limited searches.

Query
START n=node(3)
MATCH n--m
WITH m
ORDER BY m.name desc
LIMIT 1
MATCH m--o
RETURN o.name

Starting at Anders, find all matching nodes, order by name descending and get the top result, then find all the nodes connected to that top result, and return their names.

Result
o.name
2 rows
1 ms

"Anders"

"Bossman"

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start n=node(3) match n--m with m order by m.name desc limit 1 match m--o return o.name

Alternative syntax of WITH

If you prefer a more visual way of writing your query, you can use equal-signs as delimiters before and after the column list. Use at least three before the column list, and at least three after.

Query
START david=node(1)
MATCH david--otherPerson-->()
========== otherPerson, count(*) as foaf ==========
SET otherPerson.connection_count = foaf

For persons connected to David, the connection_count property is set to their number of outgoing relationships.

Result
Properties set: 2
0 ms

(empty result)

Try this query live(1) {"name":"David"} (2) {"name":"Emil"} (3) {"name":"Anders"} (4) {"name":"Bossman"} (5) {"name":"Cesar"} (1)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (3)-[:BLOCKS]->(5) {} (4)-[:KNOWS]->(2) {} (4)-[:BLOCKS]->(1) {} (5)-[:KNOWS]->(2) {} start david=node(1) match david--otherPerson-->() ========== otherPerson, count(*) as foaf ========== set otherPerson.connection_count = foaf

Create

La création d'éléments de graphe — noeuds et relations, est effectuée avec CREATE.

Astuce
Dans la clause CREATE, les patterns sont beaucoup utilisés. Lisez [introduction-pattern] pour une introduction.

Create single node

Creating a single node is done by issuing the following query.

Query
CREATE n

Nothing is returned from this query, except the count of affected nodes.

Result
Nodes created: 1
0 ms

(empty result)

Try this query live(0) create n

Create single node and set properties

The values for the properties can be any scalar expressions.

Query
CREATE n = {name : 'Andres', title : 'Developer'}

Nothing is returned from this query.

Result
Nodes created: 1
Properties set: 2
1 ms

(empty result)

Try this query live(0) create n = {name : 'Andres', title : 'Developer'}

Return created node

Creating a single node is done by issuing the following query.

Query
CREATE (a {name : 'Andres'})
RETURN a

The newly created node is returned. This query uses the alternative syntax for single node creation.

Result
a
1 row
Nodes created: 1
Properties set: 1
2 ms

Node[2]{name:"Andres"}

Try this query live(0) create (a {name : 'Andres'}) return a

Create a relationship between two nodes

To create a relationship between two nodes, we first get the two nodes. Once the nodes are loaded, we simply create a relationship between them.

Query
START a=node(1), b=node(2)
CREATE a-[r:RELTYPE]->b
RETURN r

The created relationship is returned by the query.

Result
r
1 row
Relationships created: 1
3 ms

:RELTYPE[1] {}

Try this query live(1) {} (2) {} start a=node(1), b=node(2) create a-[r:RELTYPE]->b return r

Create a relationship and set properties

Setting properties on relationships is done in a similar manner to how it’s done when creating nodes. Note that the values can be any expression.

Query
START a=node(1), b=node(2)
CREATE a-[r:RELTYPE {name : a.name + '<->' + b.name }]->b
RETURN r

The newly created relationship is returned by the example query.

Result
r
1 row
Relationships created: 1
Properties set: 1
2 ms

:RELTYPE[1] {name:"Andres<->Michael"}

Try this query live(1) {"name":"Andres"} (2) {"name":"Michael"} start a=node(1), b=node(2) create a-[r:RELTYPE {name : a.name + '<->' + b.name }]->b return r

Create a full path

When you use CREATE and a pattern, all parts of the pattern that are not already in scope at this time will be created.

Query
CREATE p = (andres {name:'Andres'})-[:WORKS_AT]->neo<-[:WORKS_AT]-(michael {name:'Michael'})
RETURN p

This query creates three nodes and two relationships in one go, assigns it to a path identifier, and returns it.

Result
p
1 row
Nodes created: 3
Relationships created: 2
Properties set: 2
4 ms

[Node[4]{name:"Andres"},:WORKS_AT[2] {},Node[5]{},:WORKS_AT[3] {},Node[6]{name:"Michael"}]

Try this query live(0) create p = (andres {name:'Andres'})-[:WORKS_AT]->neo<-[:WORKS_AT]-(michael {name:'Michael'}) return p

Création d’un simple noeud depuis une map

Vous pouvez également créer une entité de graphe depuis une Map<String,Object> map. Toutes les paires clé/valeur dans la map seront affectées comme propriétés sur le noeud ou la relation créé.

Requête
create ({props})

Cette requête peut être utilisée de la façon suivante:

Map props = new HashMap();
props.put( "name", "Andres" );
props.put( "position", "Developer" );

Map params = new HashMap();
params.put( "props", props );
engine.execute( "create ({props})", params );

Création de noeuds multiples depuis des maps

En fournissant une itération de maps (Iterable<Map<String,Object>>), Cypher créera un noeud pour chaque map dans l’itération. Quand vous faites cela, vous ne pouvez créer rien d’autre dans le même déclaratif create.

Requête
create (n {props}) return n

Cette requête peut être utilisée de la façon suivante:

Map n1 = new HashMap();
n1.put( "name", "Andres" );
n1.put( "position", "Developer" );

Map n2 = new HashMap();
n2.put( "name", "Michael" );
n2.put( "position", "Developer" );

Map params = new HashMap();
List> maps = Arrays.asList( n1, n2 );
params.put( "props", maps );
engine.execute( "create (n {props}) return n", params );

Create Unique

CREATE UNIQUE is in the middle of MATCH and CREATE — it will match what it can, and create what is missing. CREATE UNIQUE will always make the least change possible to the graph — if it can use parts of the existing graph, it will.

Another difference to MATCH is that CREATE UNIQUE assumes the pattern to be unique. If multiple matching subgraphs are found an exception will be thrown.

Astuce
In the CREATE UNIQUE clause, patterns are used a lot. Read [introduction-pattern] for an introduction.

The examples start out with the following data set:

cypher-createunique-graph.svg

Create relationship if it is missing

CREATE UNIQUE is used to describe the pattern that should be found or created.

Query
START left=node(1), right=node(3,4)
CREATE UNIQUE left-[r:KNOWS]->right
RETURN r

The left node is matched agains the two right nodes. One relationship already exists and can be matched, and the other relationship is created before it is returned.

Result
r
2 rows
Relationships created: 1
1 ms

:KNOWS[5] {}

:KNOWS[3] {}

Try this query live(1) {"name":"A"} (2) {"name":"root"} (3) {"name":"B"} (4) {"name":"C"} (1)-[:KNOWS]->(4) {} (2)-[:X]->(1) {} (2)-[:X]->(3) {} (2)-[:X]->(4) {} start left=node(1), right=node(3,4) create unique left-[r:KNOWS]->right return r

Create node if missing

If the pattern described needs a node, and it can’t be matched, a new node will be created.

Query
START root=node(2)
CREATE UNIQUE root-[:LOVES]-someone
RETURN someone

The root node doesn’t have any LOVES relationships, and so a node is created, and also a relationship to that node.

Result
someone
1 row
Nodes created: 1
Relationships created: 1
1 ms

Node[6]{}

Try this query live(1) {"name":"A"} (2) {"name":"root"} (3) {"name":"B"} (4) {"name":"C"} (1)-[:KNOWS]->(4) {} (2)-[:X]->(1) {} (2)-[:X]->(3) {} (2)-[:X]->(4) {} start root=node(2) create unique root-[:LOVES]-someone return someone

Create nodes with values

The pattern described can also contain values on the node. These are given using the following syntax: prop : <expression>.

Query
START root=node(2)
CREATE UNIQUE root-[:X]-(leaf {name:'D'} )
RETURN leaf

No node connected with the root node has the name D, and so a new node is created to match the pattern.

Result
leaf
1 row
Nodes created: 1
Relationships created: 1
Properties set: 1
3 ms

Node[6]{name:"D"}

Try this query live(1) {"name":"A"} (2) {"name":"root"} (3) {"name":"B"} (4) {"name":"C"} (1)-[:KNOWS]->(4) {} (2)-[:X]->(1) {} (2)-[:X]->(3) {} (2)-[:X]->(4) {} start root=node(2) create unique root-[:X]-(leaf {name:'D'} ) return leaf

Create relationship with values

Relationships to be created can also be matched on values.

Query
START root=node(2)
CREATE UNIQUE root-[r:X {since:'forever'}]-()
RETURN r

In this example, we want the relationship to have a value, and since no such relationship can be found, a new node and relationship are created. Note that since we are not interested in the created node, we don’t name it.

Result
r
1 row
Nodes created: 1
Relationships created: 1
Properties set: 1
1 ms

:X[5] {since:"forever"}

Try this query live(1) {"name":"A"} (2) {"name":"root"} (3) {"name":"B"} (4) {"name":"C"} (1)-[:KNOWS]->(4) {} (2)-[:X]->(1) {} (2)-[:X]->(3) {} (2)-[:X]->(4) {} start root=node(2) create unique root-[r:X {since:'forever'}]-() return r

Describe complex pattern

The pattern described by CREATE UNIQUE can be separated by commas, just like in MATCH and CREATE.

Query
START root=node(2)
CREATE UNIQUE root-[:FOO]->x, root-[:BAR]->x
RETURN x

This example pattern uses two paths, separated by a comma.

Result
x
1 row
Nodes created: 1
Relationships created: 2
1 ms

Node[6]{}

Try this query live(1) {"name":"A"} (2) {"name":"root"} (3) {"name":"B"} (4) {"name":"C"} (1)-[:KNOWS]->(4) {} (2)-[:X]->(1) {} (2)-[:X]->(3) {} (2)-[:X]->(4) {} start root=node(2) create unique root-[:FOO]->x, root-[:BAR]->x return x

Set

Updating properties on nodes and relationships is done with the SET clause. SET can also be used with maps from parameters.

The examples use this graph as a starting point:

cypher-set-graph.svg

Set a property

To set a property on a node or relationship, use SET.

Query
START n = node(2)
SET n.surname = 'Taylor'
RETURN n

The newly changed node is returned by the query.

Result
n
1 row
Properties set: 1
0 ms

Node[2]{name:"Andres",age:36,awesome:true,surname:"Taylor"}

Try this query live(1) {"age":34,"name":"Peter"} (2) {"age":36,"awesome":true,"name":"Andres"} (2)-[:KNOWS]->(1) {} start n = node(2) set n.surname = 'Taylor' return n

Remove a property

Normally you remove a property by using delete, but it’s sometimes handy to do it using the SET command. One example is if the property comes from a parameter.

Query
START n = node(2)
SET n.name = null
RETURN n

The node is returned by the query, and the name property is now missing.

Result
n
1 row
Properties set: 1
2 ms

Node[2]{age:36,awesome:true}

Try this query live(1) {"age":34,"name":"Peter"} (2) {"age":36,"awesome":true,"name":"Andres"} (2)-[:KNOWS]->(1) {} start n = node(2) set n.name = null return n

Copying properties between nodes and relationships

You can also use SET to copy all properties from one graph element to another. Remember that doing this will remove all other properties on the receiving graph element.

Query
START at = node(2), pn = node(1)
SET at = pn
RETURN at, pn

The Andres node has had all it’s properties replaced by the properties in the Peter node.

Result
atpn
1 row
Properties set: 3
5 ms

Node[2]{name:"Peter",age:34}

Node[1]{name:"Peter",age:34}

Try this query live(1) {"age":34,"name":"Peter"} (2) {"age":36,"awesome":true,"name":"Andres"} (2)-[:KNOWS]->(1) {} start at = node(2), pn = node(1) set at = pn return at, pn

Delete

Removing graph elements — nodes, relationships and properties, is done with DELETE.

The examples start out with the following database:

cypher-delete-graph.svg

Delete single node

To remove a node from the graph, you can delete it with the DELETE clause.

Query
START n = node(4)
DELETE n

Nothing is returned from this query, except the count of affected nodes.

Result
Nodes deleted: 1
0 ms

(empty result)

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"name":"Andres"} (4) {} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n = node(4) delete n

Remove a node and connected relationships

If you are trying to remove a node with relationships on it, you have to remove these as well.

Query
START n = node(3)
MATCH n-[r]-()
DELETE n, r

Nothing is returned from this query, except the count of affected nodes.

Result
Nodes deleted: 1
Relationships deleted: 2
1 ms

(empty result)

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start n = node(3) match n-[r]-() delete n, r

Remove a property

Neo4j doesn’t allow storing null in properties. Instead, if no value exists, the property is just not there. So, to remove a property value on a node or a relationship, is also done with DELETE.

Query
START andres = node(3)
DELETE andres.age
RETURN andres

The node is returned, and no property age exists on it.

Result
andres
1 row
Properties set: 1
2 ms

Node[3]{name:"Andres"}

Try this query live(1) {"age":25,"name":"Tobias"} (2) {"age":34,"name":"Peter"} (3) {"age":36,"name":"Andres"} (3)-[:KNOWS]->(1) {} (3)-[:KNOWS]->(2) {} start andres = node(3) delete andres.age return andres

Foreach

Collections and paths are key concepts in Cypher. To use them for updating data, you can use the FOREACH construct. It allows you to do updating commands on elements in a collection — a path, or a collection created by aggregation.

The identifier context inside of the foreach parenthesis is separate from the one outside it, i.e. if you CREATE a node identifier inside of a FOREACH, you will not be able to use it outside of the foreach statement, unless you match to find it.

Inside of the FOREACH parentheses, you can do any updating commands — CREATE, CREATE UNIQUE, DELETE, and FOREACH.

Mark all nodes along a path

This query will set the property marked to true on all nodes along a path.

Query
START begin = node(2), end = node(1)
MATCH p = begin -[*]-> end foreach(n in nodes(p) :
SET n.marked = true)

Nothing is returned from this query.

Result
Properties set: 4
0 ms

(empty result)

Try this query live(1) {"name":"D"} (2) {"name":"A"} (3) {"name":"B"} (4) {"name":"C"} (2)-[:KNOWS]->(3) {} (3)-[:KNOWS]->(4) {} (4)-[:KNOWS]->(1) {} start begin = node(2), end = node(1) match p = begin -[*]-> end foreach(n in nodes(p) : set n.marked = true)

Functions

Most functions in Cypher will return null if the input parameter is null.

Here is a list of the functions in Cypher, seperated into three different sections: Predicates, Scalar functions and Aggregated functions

cypher-functions-graph.svg
Figure 21. Graph

Predicates

Predicates are boolean functions that return true or false for a given set of input. They are most commonly used to filter out subgraphs in the WHERE part of a query.

ALL

Tests whether a predicate holds for all element of this collection collection.

Syntax: ALL(identifier in collection WHERE predicate)

Arguments:

  • collection: An expression that returns a collection

  • identifier: This is the identifier that can be used from the predicate.

  • predicate: A predicate that is tested against all items in the collection.

Query
START a=node(3), b=node(1)
MATCH p=a-[*1..3]->b
WHERE all(x in nodes(p)
WHERE x.age > 30)
RETURN p

All nodes in the returned paths will have an age property of at least 30.

Result
p
1 row
1 ms

[Node[3]{name:"A",age:38,eyes:"brown"},:KNOWS[1] {},Node[5]{name:"C",age:53,eyes:"green"},:KNOWS[3] {},Node[1]{name:"D",age:54,eyes:"brown"}]

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(3), b=node(1) match p=a-[*1..3]->b where all(x in nodes(p) WHERE x.age > 30) return p

ANY

Tests whether a predicate holds for at least one element in the collection.

Syntax: ANY(identifier in collection WHERE predicate)

Arguments:

  • collection: An expression that returns a collection

  • identifier: This is the identifier that can be used from the predicate.

  • predicate: A predicate that is tested against all items in the collection.

Query
START a=node(2)
WHERE any(x in a.array
WHERE x = "one")
RETURN a

All nodes in the returned paths has at least one one value set in the array property named array.

Result
a
1 row
0 ms

Node[2]{name:"E",age:41,eyes:"blue",array:["one","two","three"]}

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(2) where any(x in a.array WHERE x = "one") return a

NONE

Returns true if the predicate holds for no element in the collection.

Syntax: NONE(identifier in collection WHERE predicate)

Arguments:

  • collection: An expression that returns a collection

  • identifier: This is the identifier that can be used from the predicate.

  • predicate: A predicate that is tested against all items in the collection.

Query
START n=node(3)
MATCH p=n-[*1..3]->b
WHERE NONE(x in nodes(p)
WHERE x.age = 25)
RETURN p

No nodes in the returned paths has a age property set to 25.

Result
p
2 rows
2 ms

[Node[3]{name:"A",age:38,eyes:"brown"},:KNOWS[1] {},Node[5]{name:"C",age:53,eyes:"green"}]

[Node[3]{name:"A",age:38,eyes:"brown"},:KNOWS[1] {},Node[5]{name:"C",age:53,eyes:"green"},:KNOWS[3] {},Node[1]{name:"D",age:54,eyes:"brown"}]

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(3) match p=n-[*1..3]->b where NONE(x in nodes(p) WHERE x.age = 25) return p

SINGLE

Returns true if the predicate holds for exactly one of the elements in the collection.

Syntax: SINGLE(identifier in collection WHERE predicate)

Arguments:

  • collection: An expression that returns a collection

  • identifier: This is the identifier that can be used from the predicate.

  • predicate: A predicate that is tested against all items in the collection.

Query
START n=node(3)
MATCH p=n-->b
WHERE SINGLE(var in nodes(p)
WHERE var.eyes = "blue")
RETURN p

Exactly one node in every returned path will have the eyes property set to "blue".

Result
p
1 row
1 ms

[Node[3]{name:"A",age:38,eyes:"brown"},:KNOWS[0] {},Node[4]{name:"B",age:25,eyes:"blue"}]

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(3) match p=n-->b where SINGLE(var in nodes(p) WHERE var.eyes = "blue") return p

Scalar functions

Scalar functions return a single value.

LENGTH

To return or filter on the length of a collection, use the LENGTH() function.

Syntax: LENGTH( collection )

Arguments:

  • collection: An expression that returns a collection

Query
START a=node(3)
MATCH p=a-->b-->c
RETURN length(p)

The length of the path p is returned by the query.

Result
length(p)
3 rows
1 ms

2

2

2

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(3) match p=a-->b-->c return length(p)

TYPE

Returns a string representation of the relationship type.

Syntax: TYPE( relationship )

Arguments:

  • relationship: A relationship.

Query
START n=node(3)
MATCH (n)-[r]->()
RETURN type(r)

The relationship type of r is returned by the query.

Result
type(r)
2 rows
1 ms

"KNOWS"

"KNOWS"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(3) match (n)-[r]->() return type(r)

ID

Returns the id of the relationship or node.

Syntax: ID( property-container )

Arguments:

  • property-container: A node or a relationship.

Query
START a=node(3, 4, 5)
RETURN ID(a)

This returns the node id for three nodes.

Result
ID(a)
3 rows
0 ms

3

4

5

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(3, 4, 5) return ID(a)

COALESCE

Returns the first non-null value in the list of expressions passed to it.

Syntax: COALESCE( expression [, expression]* )

Arguments:

  • expression: The expression that might return null.

Query
START a=node(3)
RETURN coalesce(a.hairColour?, a.eyes?)
Result
coalesce(a.hairColour?, a.eyes?)
1 row
0 ms

"brown"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(3) return coalesce(a.hairColour?, a.eyes?)

HEAD

HEAD returns the first element in a collection.

Syntax: HEAD( expression )

Arguments:

  • expression: This expression should return a collection of some kind.

Query
START a=node(2)
RETURN a.array, head(a.array)

The first node in the path is returned.

Result
a.arrayhead(a.array)
1 row
1 ms

["one","two","three"]

"one"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(2) return a.array, head(a.array)

LAST

LAST returns the last element in a collection.

Syntax: LAST( expression )

Arguments:

  • expression: This expression should return a collection of some kind.

Query
START a=node(2)
RETURN a.array, last(a.array)

The last node in the path is returned.

Result
a.arraylast(a.array)
1 row
0 ms

["one","two","three"]

"three"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(2) return a.array, last(a.array)

Collection functions

Collection functions return collections of things — nodes in a path, and so on.

NODES

Returns all nodes in a path.

Syntax: NODES( path )

Arguments:

  • path: A path.

Query
START a=node(3), c=node(2)
MATCH p=a-->b-->c
RETURN NODES(p)

All the nodes in the path p are returned by the example query.

Result
NODES(p)
1 row
1 ms

[Node[3]{name:"A",age:38,eyes:"brown"},Node[4]{name:"B",age:25,eyes:"blue"},Node[2]{name:"E",age:41,eyes:"blue",array:["one","two","three"]}]

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(3), c=node(2) match p=a-->b-->c return NODES(p)

RELATIONSHIPS

Returns all relationships in a path.

Syntax: RELATIONSHIPS( path )

Arguments:

  • path: A path.

Query
START a=node(3), c=node(2)
MATCH p=a-->b-->c
RETURN RELATIONSHIPS(p)

All the relationships in the path p are returned.

Result
RELATIONSHIPS(p)
1 row
1 ms

[:KNOWS[0] {},:MARRIED[4] {}]

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(3), c=node(2) match p=a-->b-->c return RELATIONSHIPS(p)

EXTRACT

To return a single property, or the value of a function from a collection of nodes or relationships, you can use EXTRACT. It will go through a collection, run an expression on every element, and return the results in an collection with these values. It works like the map method in functional languages such as Lisp and Scala.

Syntax: EXTRACT( identifier in collection : expression )

Arguments:

  • collection: An expression that returns a collection

  • identifier: The closure will have an identifier introduced in it’s context. Here you decide which identifier to use.

  • expression: This expression will run once per value in the collection, and produces the result collection.

Query
START a=node(3), b=node(4), c=node(1)
MATCH p=a-->b-->c
RETURN extract(n in nodes(p) : n.age)

The age property of all nodes in the path are returned.

Result
extract(n in nodes(p) : n.age)
1 row
1 ms

[38,25,54]

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(3), b=node(4), c=node(1) match p=a-->b-->c return extract(n in nodes(p) : n.age)

FILTER

FILTER returns all the elements in a collection that comply to a predicate.

Syntax: FILTER(identifier in collection : predicate)

Arguments:

  • collection: An expression that returns a collection

  • identifier: This is the identifier that can be used from the predicate.

  • predicate: A predicate that is tested against all items in the collection.

Query
START a=node(2)
RETURN a.array, filter(x in a.array : length(x) = 3)

This returns the property named array and a list of values in it, which have the length 3.

Result
a.arrayfilter(x in a.array : length(x) = 3)
1 row
1 ms

["one","two","three"]

["one","two"]

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(2) return a.array, filter(x in a.array : length(x) = 3)

TAIL

TAIL returns all but the first element in a collection.

Syntax: TAIL( expression )

Arguments:

  • expression: This expression should return a collection of some kind.

Query
START a=node(2)
RETURN a.array, tail(a.array)

This returns the property named array and all elements of that property except the first one.

Result
a.arraytail(a.array)
1 row
1 ms

["one","two","three"]

["two","three"]

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(2) return a.array, tail(a.array)

RANGE

Returns numerical values in a range with a non-zero step value step. Range is inclusive in both ends.

Syntax: RANGE( start, end [, step] )

Arguments:

  • start: A numerical expression.

  • end: A numerical expression.

  • step: A numerical expression.

Query
START n=node(1)
RETURN range(0,10), range(2,18,3)

Two lists of numbers are returned.

Result
range(0,10)range(2,18,3)
1 row
0 ms

[0,1,2,3,4,5,6,7,8,9,10]

[2,5,8,11,14,17]

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return range(0,10), range(2,18,3)

REDUCE

To run an expression against individual elements of a collection, and store the result of the expression in an accumulator, you can use REDUCE. It will go through a collection, run an expression on every element, storing the partial result in the accumulator. It works like the fold or reduce method in functional languages such as Lisp and Scala.

Syntax: REDUCE( accumulator = initial, identifier in collection : expression )

Arguments:

  • accumulator: An identifier that will hold the result and the partial results as the collection is iterated

  • initial: An expression that runs once to give a starting value to the accumulator

  • collection: An expression that returns a collection

  • identifier: The closure will have an identifier introduced in it’s context. Here you decide which identifier to use.

  • expression: This expression will run once per value in the collection, and produces the result value.

Query
START a=node(3), b=node(4), c=node(1)
MATCH p=a-->b-->c
RETURN reduce(totalAge = 0, n in nodes(p) : totalAge + n.age)

The age property of all nodes in the path are summed and returned as a single value.

Result
reduce(totalAge = 0, n in nodes(p) : totalAge + n.age)
1 row
1 ms

117

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(3), b=node(4), c=node(1) match p=a-->b-->c return reduce(totalAge = 0, n in nodes(p) : totalAge + n.age)

Mathematical functions

These functions all operate on numerical expressions only, and will return an error if used on any other values.

ABS

ABS returns the absolute value of a number.

Syntax: ABS( expression )

Arguments:

  • expression: A numeric expression.

Query
START a=node(3), c=node(2)
RETURN a.age, c.age, abs(a.age - c.age)

The absolute value of the age difference is returned.

Result
a.agec.ageabs(a.age - c.age)
1 row
0 ms

38

41

3.0

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(3), c=node(2) return a.age, c.age, abs(a.age - c.age)

ROUND

ROUND returns the numerical expression, rounded to the nearest integer.

Syntax: ROUND( expression )

Arguments:

  • expression: A numerical expression.

Query
START a=node(1)
RETURN round(3.141592)
Result
round(3.141592)
1 row
1 ms

3

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(1) return round(3.141592)

SQRT

SQRT returns the square root of a number.

Syntax: SQRT( expression )

Arguments:

  • expression: A numerical expression

Query
START a=node(1)
RETURN sqrt(256)
Result
sqrt(256)
1 row
0 ms

16.0

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start a=node(1) return sqrt(256)

SIGN

SIGN returns the signum of a number — zero if the expression is zero, -1 for any negative number, and 1 for any positive number.

Syntax: SIGN( expression )

Arguments:

  • expression: A numerical expression

Query
START n=node(1)
RETURN sign(-17), sign(0.1)
Result
sign(-17)sign(0.1)
1 row
0 ms

-1.0

1.0

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return sign(-17), sign(0.1)

String functions

These functions all operate on string expressions only, and will return an error if used on any other values. Except STR(), which converts to strings.

STR

STR returns a string representation of the expression.

Syntax: STR( expression )

Arguments:

  • expression: An expression that returns anything

Query
START n=node(1)
RETURN str(1)

A string.

Result
str(1)
1 row
0 ms

"1"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return str(1)

REPLACE

REPLACE returns a string with the search string replaced by the replace string. It replaces all occurrences.

Syntax: REPLACE( original, search, replace )

Arguments:

  • original: An expression that returns a string

  • search: An expression that returns a string to search for

  • replace: An expression that returns the string to replace the search string with

Query
START n=node(1)
RETURN replace("hello", "l", "w")

A string.

Result
replace("hello", "l", "w")
1 row
0 ms

"hewwo"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return replace("hello", "l", "w")

SUBSTRING

SUBSTRING returns a substring of the original, with a 0-based index start and length. If length is omitted, it returns a substring from start until the end of the string.

Syntax: SUBSTRING( original, start [, length] )

Arguments:

  • original: An expression that returns a string

  • start: An expression that returns a positive number

  • length: An expression that returns a positive number

Query
START n=node(1)
RETURN substring("hello", 1, 3), substring("hello", 2)

A string.

Result
substring("hello", 1, 3)substring("hello", 2)
1 row
0 ms

"ell"

"llo"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return substring("hello", 1, 3), substring("hello", 2)

LEFT

LEFT returns a string containing the left n characters of the original string.

Syntax: LEFT( original, length )

Arguments:

  • original: An expression that returns a string

  • n: An expression that returns a positive number

Query
START n=node(1)
RETURN left("hello", 3)

A String.

Result
left("hello", 3)
1 row
0 ms

"hel"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return left("hello", 3)

RIGHT

RIGHT returns a string containing the right n characters of the original string.

Syntax: RIGHT( original, length )

Arguments:

  • original: An expression that returns a string

  • n: An expression that returns a positive number

Query
START n=node(1)
RETURN right("hello", 3)

A string.

Result
right("hello", 3)
1 row
0 ms

"llo"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return right("hello", 3)

LTRIM

LTRIM returns the original string with whitespace removed from the left side.

Syntax: LTRIM( original )

Arguments:

  • original: An expression that returns a string

Query
START n=node(1)
RETURN ltrim("   hello")

A string.

Result
ltrim(" hello")
1 row
0 ms

"hello"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return ltrim(" hello")

RTRIM

RTRIM returns the original string with whitespace removed from the right side.

Syntax: RTRIM( original )

Arguments:

  • original: An expression that returns a string

Query
START n=node(1)
RETURN rtrim("hello   ")

A string.

Result
rtrim("hello ")
1 row
0 ms

"hello"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return rtrim("hello ")

TRIM

TRIM returns the original string with whitespace removed from both sides.

Syntax: TRIM( original )

Arguments:

  • original: An expression that returns a string

Query
START n=node(1)
RETURN trim("   hello   ")

A string.

Result
trim(" hello ")
1 row
0 ms

"hello"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return trim(" hello ")

LOWER

LOWER returns the original string in lowercase.

Syntax: LOWER( original )

Arguments:

  • original: An expression that returns a string

Query
START n=node(1)
RETURN lower("HELLO")

A string.

Result
lower("HELLO")
1 row
1 ms

"hello"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return lower("HELLO")

UPPER

UPPER returns the original string in uppercase.

Syntax: UPPER( original )

Arguments:

  • original: An expression that returns a string

Query
START n=node(1)
RETURN upper("hello")

A string.

Result
upper("hello")
1 row
0 ms

"HELLO"

Try this query live(1) {"age":54,"eyes":"brown","name":"D"} (2) {"age":41,"array":["one","two","three"],"eyes":"blue","name":"E"} (3) {"age":38,"eyes":"brown","name":"A"} (4) {"age":25,"eyes":"blue","name":"B"} (5) {"age":53,"eyes":"green","name":"C"} (3)-[:KNOWS]->(4) {} (3)-[:KNOWS]->(5) {} (4)-[:KNOWS]->(1) {} (4)-[:MARRIED]->(2) {} (5)-[:KNOWS]->(1) {} start n=node(1) return upper("hello")

Compatibility

Cypher is still changing rather rapidly. Parts of the changes are internal — we add new pattern matchers, aggregators and other optimizations, which hopefully makes your queries run faster.

Other changes are directly visible to our users — the syntax is still changing. New concepts are being added and old ones changed to fit into new possibilities. To guard you from having to keep up with our syntax changes, Cypher allows you to use an older parser, but still gain the speed from new optimizations.

There are two ways you can select which parser to use. You can configure your database with the configuration parameter cypher_parser_version, and enter which parser you’d like to use (1.7, 1.8 and 1.9 are supported now). Any Cypher query that doesn’t explicitly say anything else, will get the parser you have configured.

The other way is on a query by query basis. By simply pre-pending your query with "CYPHER 1.7", that particular query will be parsed with the 1.7 version of the parser. Example:

CYPHER 1.7 START n=node(0)
WHERE n.foo = "bar"
RETURN n

From SQL to Cypher

This guide is for people who understand SQL. You can use that prior knowledge to quickly get going with Cypher and start exploring Neo4j.

Start

SQL starts with the result you want — we SELECT what we want and then declare how to source it. In Cypher, the START clause is quite a different concept which specifies starting points in the graph from which the query will execute.

From a SQL point of view, the identifiers in START are like table names that point to a set of nodes or relationships. The set can be listed literally, come via parameters, or as I show in the following example, be defined by an index look-up.

So in fact rather than being SELECT-like, the START clause is somewhere between the FROM and the WHERE clause in SQL.

SQL Query
SELECT *
FROM "Person"
WHERE name = 'Anakin'
NAMEIDAGEHAIR
1 rows

Anakin

1

20

blonde

Cypher Query
START person=node:Person(name = 'Anakin')
RETURN person
person
1 row
19 ms

Node[1]{name:"Anakin",id:1,age:20,hair:"blonde"}

Cypher allows multiple starting points. This should not be strange from a SQL perspective — every table in the FROM clause is another starting point.

Match

Unlike SQL which operates on sets, Cypher predominantly works on sub-graphs. The relational equivalent is the current set of tuples being evaluated during a SELECT query.

The shape of the sub-graph is specified in the MATCH clause. The MATCH clause is analogous to the JOIN in SQL. A normal a→b relationship is an inner join between nodes a and b — both sides have to have at least one match, or nothing is returned.

We’ll start with a simple example, where we find all email addresses that are connected to the person «Anakin». This is an ordinary one-to-many relationship.

SQL Query
SELECT "Email".*
FROM "Person"
JOIN "Email" ON "Person".id = "Email".person_id
WHERE "Person".name = 'Anakin'
ADDRESSCOMMENTPERSON_ID
2 rows

anakin@example.com

home

1

anakin@example.org

work

1

Cypher Query
START person=node:Person(name = 'Anakin')
MATCH person-[:email]->email
RETURN email
email
2 rows
35 ms

Node[7]{address:"anakin@example.com",comment:"home"}

Node[8]{address:"anakin@example.org",comment:"work"}

There is no join table here, but if one is necessary the next example will show how to do that, writing the pattern relationship like so: -[r:belongs_to]-> will introduce (the equivalent of) join table available as the variable r. In reality this is a named relationship in Cypher, so we’re saying «join Person to Group via belongs_to.» To illustrate this, consider this image, comparing the SQL model and Neo4j/Cypher.

RDBMSvsGraph.svg.png

And here are example queries:

SQL Query
SELECT "Group".*, "Person_Group".*
FROM "Person"
JOIN "Person_Group" ON "Person".id = "Person_Group".person_id
JOIN "Group" ON "Person_Group".Group_id="Group".id
WHERE "Person".name = 'Bridget'
NAMEIDBELONGS_TO_GROUP_IDPERSON_IDGROUP_ID
1 rows

Admin

4

3

2

4

Cypher Query
START person=node:Person(name = 'Bridget')
MATCH person-[r:belongs_to]->group
RETURN group, r
groupr
1 row
1 ms

Node[6]{name:"Admin",id:4}

:belongs_to[0] {}

An outer join is just as easy. Add a question mark -[?:KNOWS]-> and it’s an optional relationship between nodes — the outer join of Cypher.

Whether it’s a left outer join, or a right outer join is defined by which side of the pattern has a starting point. This example is a left outer join, because the bound node is on the left side:

SQL Query
SELECT "Person".name, "Email".address
FROM "Person" LEFT
JOIN "Email" ON "Person".id = "Email".person_id
NAMEADDRESS
3 rows

Anakin

anakin@example.com

Anakin

anakin@example.org

Bridget

<null>

Cypher Query
START person=node:Person('name: *')
MATCH person-[?:email]->email
RETURN person.name, email.address?
person.nameemail.address?
3 rows
48 ms

"Anakin"

"anakin@example.com"

"Anakin"

"anakin@example.org"

"Bridget"

<null>

Relationships in Neo4j are first class citizens — it’s like the SQL tables are pre-joined with each other. So, naturally, Cypher is designed to be able to handle highly connected data easily.

One such domain is tree structures — anyone that has tried storing tree structures in SQL knows that you have to work hard to get around the limitations of the relational model. There are even books on the subject.

To find all the groups and sub-groups that Bridget belongs to, this query is enough in Cypher:

Cypher Query
START person=node:Person('name: Bridget')
MATCH person-[:belongs_to*]->group
RETURN person.name, group.name
person.namegroup.name
3 rows
13 ms

"Bridget"

"Admin"

"Bridget"

"Technichian"

"Bridget"

"User"

The * after the relationship type means that there can be multiple hops across belongs_to relationships between group and user. Some SQL dialects have recursive abilities, that allow the expression of queries like this, but you may have a hard time wrapping your head around those. Expressing something like this in SQL is hugely impractical if not practically impossible.

Where

This is the easiest thing to understand — it’s the same animal in both languages. It filters out result sets/subgraphs. Not all predicates have an equivalent in the other language, but the concept is the same.

SQL Query
SELECT *
FROM "Person"
WHERE "Person".age > 35 AND "Person".hair = 'blonde'
NAMEIDAGEHAIR
1 rows

Bridget

2

40

blonde

Cypher Query
START person=node:Person('name: *')
WHERE person.age > 35 AND person.hair = 'blonde'
RETURN person
person
1 row
1 ms

Node[2]{name:"Bridget",id:2,age:40,hair:"blonde"}

Return

This is SQL’s SELECT. We just put it in the end because it felt better to have it there — you do a lot of matching and filtering, and finally, you return something.

Aggregate queries work just like they do in SQL, apart from the fact that there is no explicit GROUP BY clause. Everything in the return clause that is not an aggregate function will be used as the grouping columns.

SQL Query
SELECT "Person".name, count(*)
FROM "Person"
GROUP BY "Person".name
ORDER BY "Person".name
NAMEC2
2 rows

Anakin

1

Bridget

1

Cypher Query
START person=node:Person('name: *')
RETURN person.name, count(*)
ORDER BY person.name
person.namecount(*)
2 rows
0 ms

"Anakin"

1

"Bridget"

1

Order by is the same in both languages — ORDER BY expression ASC/DESC. Nothing weird here.

Appendice A: Questions & Réponses

  1. Quel est le nombre maximum de noeuds supportés? Quel est le nombre maximum de relations supportées par noeud?

    At the moment it is 34.4 billion nodes, 34.4 billion relationships, and at a minimum 68.7 billion properties (maximum is 274 billion, depending on the property types), in total.

  2. Quel est le plus grand graphe connecté supporté (ex: chaque noeud est connecté à tous les autres noeuds)?

    Les limites théoriques sont dépendants des chiffres ci-dessus: Basiquement cela représente un graphe complet de 262144 noeuds et 34359607296 relations. Cependant nous n’avons encore jamais rencontré ce genre de situation.

  3. Est-ce que les opérations de lecture/écriture sont dépendantes du nombre de noeuds/relations dans la BDD?

    Cette question peut signifier beaucoup de choses différentes. La performance d’une simple opération de lecture/écriture ne dépend pas de la tailler de la BDD. Le fait que le graphe contienne 10 noeuds ou 10 millions de noeuds n’a aucune importance.  — Il y a cependant un autre facteur ici, si votre graphe prend beaucoup de place sur le disque, il se peut qu’il ne sache pas être introduit dans le cache de la NVRAM. Dans ce cas, vous pourriez rencontrer des opérations sur le disque plus fréquentes. La plupart des clients n’ont pas de graphes de cette taille, mais certains en ont. Si vous devriez atteindre cette taille, nous disposons d’approches consistant à l'élargissement de la BDD sur plusieurs machines afin d’atténuer l’impact de performance en agrandissant la surface de chache entre les machines.

  4. Combien d’opérations de lecture/écriture concurrentes sont supportées?

    Il n’y a pas de limite en nombre de requêtes concurrentes. Le nombre de requêtes qui peuvent être traitées par seconde dépend principalement sur le type d’opération exécutée (opération d'écriture lourde, lecture simple, traversée complexe, etc.) et du matériel physique utilisé. Une estimation de 1000 hops par milliseconde pour une simple traversée de graphe de façon basique. Après une discussion sur le type d’usage spécifique, nous serions en mesure de donner une meilleure estimation de la performance qu’une requête demanderait.

  5. Comment est-ce que les données sont maintenues en cohérence dans un environnement de grappes de serveurs (cluster)?

    Réplication maître-esclave. Les esclaves récupèrent les changements sur le maître. L’intervalle de récupération peut-être configurée pour chaque esclave, de dixièmes de secondes en minutes, ensuite l'écriture est durable sur l’esclave et le maître. Les autres esclaves procèdent ensuite à la normale.

  6. Comment est le temps de latence pendant la mise à jour de tous les serveurs quand il y a une mise à jour sur l’un de ceux-ci?

    L’intervalle des temps de récupération peuvent être configurés par esclave, de dixièmes de secondes en minutes, selon le besoin. Quand on écrit sur un esclave, celui-ci est immédiatement synchronisé avec le maître avant que l’opération d'écriture soit exécutée sur l’esclave et le maître. En général la charge des opérations de lecture/écriture n’affecte pas les esclaves en synchronisation. Une lourde opération d'écriture occasionera cependant une grosse pression sur le système de fichiers du maître, ce qui sera également requis par les esclaves pour la lecture des changements. Cependant cela n’est pas encore apparu être un problème notable en pratique.

  7. Est-ce que l’augmentation du temps de latence est proportionnelle au nombres de serveurs dans la grappe (cluster)?

    En évoluant vers des dizaines d’esclaves sur la grappe, nous anticipons le fait que le nombre de requêtes de récupération réduira la performance du serveur maître. Seulement les opérations d'écriture sur la grappe seront affectées, les opérations de lecture continueront à s'échelonner linéairement.

  8. Est-ce que l’expansion à chaud est supportée? En d’autres termes doit-on couper tous les serveurs et la base de données afin d’ajouter un nouveau serveur sur la grappe?

    De nouveaux esclaves peuvent être ajoutés sans devoir arrêter et démarrer toute la grappe. Notre protocole de haute-disponibilité ajoutera un nouvel esclave à jour. Les esclaves peuvent également être retirés à chaud simplement en les arrêtant.

  9. Combien de temps est nécessaire pour qu’un nouvel esclave soit synchronisé?

    Nous vous recommandons de munir votre nouvel esclave d’une version snapshot récente de la base de données avant de le mettre en ligne. Ceci est généralement réalisé pendant les opérations de sauvegarde. L’esclave ne devra alors synchroniser que les mises à jour les plus récentes, qui seront réalisées normalement en quelques secondes.

  10. Combien de temps nécessite un redémarrage?

    Si par redémarrage, vous entendez arrêter le cluster et le redémarrer, cela dépend réellement de la vitesse à laquelle vous savez taper au clavier. Donc cela devrait rester en dessous des 10 secondes. Les caches Neo4j ne seront cependant pas démarrés à chaud mais le cache du système de fichiers gardera lui ses données.

  11. Existe-t-il des solutions de sauvegarde/restauration?

    Neo4j Enterprise Edition fournit une fonctionnalité de sauvegarde en ligne. Des sauvegardes complètes ou incrémentielles peuvent être réalisées en production.

  12. Est-ce qu’un grappage cross-continental est supporté. En gros, est-ce que des serveurs en grappe localisés dans des continents différents créent une possibilité que les communications inter-continentales soient plus lentes que les intra-continentales?

    Nous avons des clients qui ont testé des déploiements multi-régions en AWS. Les temps de latence inter-continentaux seront affectés, sur les prototcoles de gestion et synchronisation des clusters. De larges latences dans le cluster peuvent déclencher des ré-élections du mâitre fréquentes, ce qui aura pour effet de ralentir le cluster. Le support de cette fonctionnalité sera amélioré dans le temps.

  13. Existe-t-il des politiques/conseils d’utilisation pour ce type de configuration?

    Nous devrions avoir une discussion détaillé à propos des spécificités et besoins relatifs à ce type de déploiement.

  14. Est-ce que l'écriture dans la BDD est sécurisée par processus? Ou est-ce à l’application de fournir une protection d'écriture dans les mêmes noeuds/relations?

    Aussi bien en instance simple qu’en mode haute-disponibilité, la base de données fournit une protection en vérouillant les noeuds et relations contre l'écriture.

  15. Quelle est la meilleure stratégie afin de récupérer les écritures en mode haute-disponibilité?

    1. Sessions permanentes

    2. Renvoyer les données dans la réponse, enlever le besoin de récupérer les écritures dans une requête séparée

    3. Forcer une récupération de mises à jour depuis le maître quand nécessaire

  16. Quelle est la meilleure stratégie pour les opérations de get-or-create?

    1. Processus unique.

    2. Si il n’existe pas, vérouillage péssimiste sur un noeud (ou un jeu de noeuds).

    3. Si il n’existe pas, création optimiste, et double contrôle après (cette explication sera étendue).

  17. Comment fonctionne le vérouillage?

    Vérouillage péssimiste. Les vérous ne sont jamais requis pour les lectures. Les écritures ne bloqueront pas les lectures. Il est impossible de réaliser des bloquages d’opérations de lecture sans utiliser des moyens de vérouillage explicite. Les vérous de lecture se répercutent les les écritures. Un vérou de lecture signifie une vue consistente pour tous les détenteurs. Les vérous d'écriture sont engendrés automatiquement quand un noeud/relations est modifié/créé, ou par des moyens de vérous explicites. *

  18. Qu’en est-il du stockage de taille?

    Neo4j n’est pour l’instant pas prévu pour stocker des BLOBs/CLOBs. Les noeuds, relations et propriétés ne sont pas co-localisées sur le disque. Cela sera peut-être introduit dans le futur.

  19. Qu’en est-il de l’indexation?

    Neo4j supporte les indices de propriétés composés. Présente des indices du graphe aux fournisseurs d’index. Le moteur Lucene gère la pagination de l’index en interne et requière sa propre mémoire. Neo4j supporte actuellement un indexeur automatique et plusieurs indexs individuels (recherche effectée via l’API);

  20. Comment est-ce que je questionne la base de données?

    Core API, API de Traversées, API ReST, Cypher, Gremlin.

  21. Comment utiliser la journalisation?

    En se basant sur les deltas de modifications d'écriture entre le maître et les esclaves sur une grappe de serveurs.

  22. Comment est-ce que j’optimise Neo4j pour la performance?

    Utilisation de fichiers de mémoire en accès linéaire (memory-mapped) Les stratégies de mise en cache de Neo4j doivent être expliquées:

    • Soft-ref cache: Les soft references sont effacées quand le GC pense que c’est nécessaire. A utiliser si la charge de l’application n’est pas très grande et qu’on a besoin d’un cache soignant la mémoire.

    • Weak-ref cache: Le GC efface les weak references quand il en trouve. A utiliser si l’application est soumise à une forte charge avec un grand nombre de lectures et de traversées.

    • Strong-ref cache: Tous les noeuds et relations sont mises dans le cache mémoire. JVM a besoin d’une pause après une forte charge, par exemple 1/2 minutes d’intervalle. Des heap sizes larges sont bons, sinon 12G et plus ne sont pas pratiques avec le GC. Une augmentation de performance de 100x avec de larges fichiers memory-mapped de cache et 1000x avec les heap sizes Java en comparaison avec les opérations de lecture/écriture sur le disque.

  23. Transactions ACID entre le maître et les esclaves?

    Synchronisé entre les opérations initiées depuis un esclave vers le maître, éventuellement de maître vers esclave. Transactions concurentes initiées depuis plusieurs esclaves supportées avec détection deadlock. Complètement consistent d’un point de vue intégrité des données, éventuellement consistent d’un point de vue synchronisation.

  24. Qu’en est-il du serveur standalone?

    L’API ReST est complètement stateless, mais peut exécuter des opérations en masse pour de grandes transactions. Pile de processus et processus par socket: POur le mode serveur standalone & noeuds HD, Neo4j utilise Jetty pour la connection de la pile (par ex: 25/noeud en cluster HD)

  25. Comment est-utilisé un Load-Balancer avec le mode HD?

    Typiquement une petite extension serveur peut être écrite afin de retourner 200 ou 404 en fonction de la machine qui est maître et de celle qui est esclave. Cette extension peut ensuite être contactée par le load balancer afin de savoir quelle machine est maître et laquelle est esclave. En écrivant exclusivement sur les esclaves, on s’assure d’avoir des opérations d'écriture à au moins deux endroits.

  26. Quel système de monitoring est fourni avec Neo4j?

    Neo4j n’a pour l’instant pas de systèmes de tracage ou de plans explicites. JMX est l’interface principale pour les statistiques et le monitoring. Les dumps de processus peuvent être utilisés pour débugger un système non fonctionnel.

  27. Comment importer les données dans Neo4j?

    The Neo4j batch inserter can be used to fill an initial database with data. After batch insertion, the store can be used in an embedded or HA environment. Future data load/refresh should go directly to Production server SQL Importer (built on top of Batch Inserter) is not officially supported.