Chapter 3. Cypher

This chapter contains the complete and authoritative documentation for the Cypher query language.

3.1. Introduction

For a short introduction, see Section 3.1.1, “What is Cypher?”. To take your first steps with Cypher, see Section 2.2, “Get started with Cypher”. For the terminology used, see Appendix B, Terminology.

3.1.1. What is Cypher?

Cypher is a declarative graph query language that allows for expressive and efficient querying and updating of the graph store. Cypher is a relatively simple but still very powerful language. Very complicated database queries can easily be expressed through Cypher. This allows you to focus on your domain instead of getting lost in database access.

Cypher is designed to be a humane query language, suitable for both developers and (importantly, we think) operations professionals. Our guiding goal is to make the simple things easy, and the complex things possible. Its constructs are based on English prose and neat iconography which helps to make queries more self-explanatory. We have tried to optimize the language for reading and not for writing.

Being a declarative language, Cypher focuses on the clarity of expressing what to retrieve from a graph, not on how to retrieve it. This is in contrast to imperative languages like Java, scripting languages like Gremlin, and the JRuby Neo4j bindings. This approach makes query optimization an implementation detail instead of burdening the user with it and requiring her to update all traversals just because the physical database structure has changed (new indexes etc.).

Cypher is inspired by a number of different approaches and builds upon established practices for expressive querying. Most of the keywords like WHERE and ORDER BY are inspired by SQL. Pattern matching borrows expression approaches from SPARQL. Some of the collection semantics have been borrowed from languages such as Haskell and Python.

Structure

Cypher borrows its structure from SQL — queries are built up using various clauses.

Clauses are chained together, and the they feed intermediate result sets between each other. For example, the matching variables from one MATCH clause will be the context that the next clause exists in.

The query language is comprised of several distinct clauses. You can read more details about them later in the manual.

Here are a few clauses used to read from the graph:

  • MATCH: The graph pattern to match. This is the most common way to get data from the graph.
  • WHERE: Not a clause in its own right, but rather part of MATCH, OPTIONAL MATCH and WITH. Adds constraints to a pattern, or filters the intermediate result passing through WITH.
  • RETURN: What to return.

Let’s see MATCH and RETURN in action.

Imagine an example graph like the following one:

Figure 3.1. Example Graph
alt

For example, here is a query which finds a user called John and John’s friends (though not his direct friends) before returning both John and any friends-of-friends that are found.

MATCH (john {name: 'John'})-[:friend]->()-[:friend]->(fof)
RETURN john.name, fof.name

Resulting in:

+----------------------+
| john.name | fof.name |
+----------------------+
| "John"    | "Maria"  |
| "John"    | "Steve"  |
+----------------------+
2 rows

Next up we will add filtering to set more parts in motion:

We take a list of user names and find all nodes with names from this list, match their friends and return only those followed users who have a name property starting with S.

MATCH (user)-[:friend]->(follower)
WHERE user.name IN ['Joe', 'John', 'Sara', 'Maria', 'Steve'] AND follower.name =~ 'S.*'
RETURN user.name, follower.name

Resulting in:

+---------------------------+
| user.name | follower.name |
+---------------------------+
| "Joe"     | "Steve"       |
| "John"    | "Sara"        |
+---------------------------+
2 rows

And here are examples of clauses that are used to update the graph:

  • CREATE (and DELETE): Create (and delete) nodes and relationships.
  • SET (and REMOVE): Set values to properties and add labels on nodes using SET and use REMOVE to remove them.
  • MERGE: Match existing or create new nodes and patterns. This is especially useful together with uniqueness constraints.

3.1.2. Querying and updating the graph

Cypher can be used for both querying and updating your graph.

3.1.2.1. The structure of update queries

  • A Cypher query part can’t both match and update the graph at the same time.
  • Every part can either read and match on the graph, or make updates on it.

If you read from the graph and then update the graph, your query implicitly has two parts — the reading is the first part, and the writing is the second part.

If your query only performs reads, Cypher will be lazy and not actually match the pattern until you ask for the results. In an updating query, the semantics are that all the reading will be done before any writing actually happens.

The only pattern where the query parts are implicit is when you first read and then write — any other order and you have to be explicit about your query parts. The parts are separated using the WITH statement. WITH is like an event horizon — it’s a barrier between a plan and the finished execution of that plan.

When you want to filter using aggregated data, you have to chain together two reading query parts — the first one does the aggregating, and the second filters on the results coming from the first one.

MATCH (n {name: 'John'})-[:FRIEND]-(friend)
WITH n, count(friend) as friendsCount
WHERE friendsCount > 3
RETURN n, friendsCount

Using WITH, you specify how you want the aggregation to happen, and that the aggregation has to be finished before Cypher can start filtering.

Here’s an example of updating the graph, writing the aggregated data to the graph:

MATCH (n {name: 'John'})-[:FRIEND]-(friend)
WITH n, count(friend) as friendsCount
SET n.friendCount = friendsCount
RETURN n.friendsCount

You can chain together as many query parts as the available memory permits.

3.1.2.2. Returning data

Any query can return data. If your query only reads, it has to return data — it serves no purpose if it doesn’t, and it is not a valid Cypher query. Queries that update the graph don’t have to return anything, but they can.

After all the parts of the query comes one final RETURN clause. RETURN is not part of any query part — it is a period symbol at the end of a query. The RETURN clause has three sub-clauses that come with it: SKIP/LIMIT and ORDER BY.

If you return graph elements from a query that has just deleted them — beware, you are holding a pointer that is no longer valid. Operations on that node are undefined.

3.1.3. Transactions

Any query that updates the graph will run in a transaction. An updating query will always either fully succeed, or not succeed at all.

Cypher will either create a new transaction or run inside an existing one:

  • If no transaction exists in the running context Cypher will create one and commit it once the query finishes.
  • In case there already exists a transaction in the running context, the query will run inside it, and nothing will be persisted to disk until that transaction is successfully committed.

This can be used to have multiple queries be committed as a single transaction:

  1. Open a transaction,
  2. run multiple updating Cypher queries,
  3. and commit all of them in one go.

Note that a query will hold the changes in memory until the whole query has finished executing. A large query will consequently need a JVM with lots of heap space.

For using transactions over the REST API, see Section 5.1, “Transactional Cypher HTTP endpoint”.

When writing server extensions or using Neo4j embedded, remember that all iterators returned from an execution result should be either fully exhausted or closed to ensure that the resources bound to them will be properly released. Resources include transactions started by the query, so failing to do so may, for example, lead to deadlocks or other weird behavior.

3.1.4. Uniqueness

While pattern matching, Neo4j makes sure to not include matches where the same graph relationship is found multiple times in a single pattern. In most use cases, this is a sensible thing to do.

Example: looking for a user’s friends of friends should not return said user.

Let’s create a few nodes and relationships:

CREATE (adam:User { name: 'Adam' }),(pernilla:User { name: 'Pernilla' }),(david:User { name: 'David'
  }),
  (adam)-[:FRIEND]->(pernilla),(pernilla)-[:FRIEND]->(david)

Which gives us the following graph:

alt

Now let’s look for friends of friends of Adam:

MATCH (user:User { name: 'Adam' })-[r1:FRIEND]-()-[r2:FRIEND]-(friend_of_a_friend)
RETURN friend_of_a_friend.name AS fofName
+---------+
| fofName |
+---------+
| "David" |
+---------+
1 row

In this query, Cypher makes sure to not return matches where the pattern relationships r1 and r2 point to the same graph relationship.

This is however not always desired. If the query should return the user, it is possible to spread the matching over multiple MATCH clauses, like so:

MATCH (user:User { name: 'Adam' })-[r1:FRIEND]-(friend)
MATCH (friend)-[r2:FRIEND]-(friend_of_a_friend)
RETURN friend_of_a_friend.name AS fofName
+---------+
| fofName |
+---------+
| "David" |
| "Adam"  |
+---------+
2 rows

Note that while the following query looks similar to the previous one, it is actually equivalent to the one before.

MATCH (user:User { name: 'Adam' })-[r1:FRIEND]-(friend),(friend)-[r2:FRIEND]-(friend_of_a_friend)
RETURN friend_of_a_friend.name AS fofName

Here, the MATCH clause has a single pattern with two paths, while the previous query has two distinct patterns.

+---------+
| fofName |
+---------+
| "David" |
+---------+
1 row

3.1.5. Compatibility

Cypher is not a static language. New versions introduce new features and sometimes old features get dropped. Older versions of the language can still be accessed if required. There are two ways to select which version to use in queries.

  1. Setting a version for all queries You can configure your database with the configuration parameter cypher.default_language_version, and enter which version you’d like to use (see Section 3.1.5.2, “Supported Language Versions”). Every Cypher query will use this version, provided the query hasn’t explicitly been configured as described in the next item below.
  2. Setting a version on a query by query basis The other way is on a query by query basis. By simply putting CYPHER 2.3 at the beginning, that particular query will be executed with the version of Cypher included in Neo4j 2.3.

Below is an example using the START clause to access a legacy index:

CYPHER 2.3
START n=node:nodes(name = "A")
RETURN n

3.1.5.1. Accessing entities by id via START

In versions of Cypher prior to 2.2 it was also possible to access specific nodes or relationships using the START clause. In this case you could use a syntax like the following:

CYPHER 1.9
START n=node(42)
RETURN n

The use of the START clause to find nodes by ID was deprecated from Cypher 2.0 onwards and is now entirely disabled in Cypher 2.2 and up. You should instead make use of the MATCH clause for starting points. See Section 3.3.1, “MATCH” for more information on the correct syntax for this. The START clause should only be used when accessing legacy indexes.

3.1.5.2. Supported Language Versions

Neo4j 3.0 supports the following versions of the Cypher language:

  • Neo4j Cypher 3.0
  • Neo4j Cypher 2.3

Each release of Neo4j supports a limited number of old Cypher Language Versions. When you upgrade to a new release of Neo4j, please make sure that it supports the Cypher language version you need. If not, you may need to modify your queries to work with a newer Cypher language version.

3.2. Syntax

This section describes the syntax of the Cypher query language.

3.2.1. Values

All values that are handled by Cypher have a distinct type. The supported types of values are:

  • Numeric values,
  • String values,
  • Boolean values,
  • Nodes,
  • Relationships,
  • Paths,
  • Maps from Strings to other values,
  • Lists of any other type of value.

Most types of values can be constructed in a query using literal expressions (see Section 3.2.2, “Expressions”). Special care must be taken when using NULL, as NULL is a value of every type (see Section 3.2.9, “Working with NULL”). Nodes, relationships, and paths are returned as a result of pattern matching.

Note that labels are not values but are a form of pattern syntax.

3.2.2. Expressions

3.2.2.1. Expressions in general

An expression in Cypher can be:

  • A decimal (integer or double) literal: 13, -40000, 3.14, 6.022E23.
  • A hexadecimal integer literal (starting with 0x): 0x13zf, 0xFC3A9, -0x66eff.
  • An octal integer literal (starting with 0): 01372, 02127, -05671.
  • A string literal: "Hello", 'World'.
  • A boolean literal: true, false, TRUE, FALSE.
  • A variable: n, x, rel, myFancyVariable, \`A name with weird stuff in it[]!`.
  • A property: n.prop, x.prop, rel.thisProperty, myFancyVariable.\`(weird property name)`.
  • A dynamic property: n["prop"], rel[n.city + n.zip], map[coll[0]].
  • A parameter: {param}, {0}
  • A list of expressions: ["a", "b"], [1,2,3], ["a", 2, n.property, {param}], [ ].
  • A function call: length(p), nodes(p).
  • An aggregate function: avg(x.prop), count(*).
  • A path-pattern: (a)-→()←-(b).
  • An operator application: 1 + 2 and 3 < 4.
  • A predicate expression is an expression that returns true or false: a.prop = "Hello", length(p) > 10, has(a.name).
  • A regular expression: a.name =~ "Tob.*"
  • A case-sensitive string matching expression: a.surname STARTS WITH "Sven", a.surname ENDS WITH "son" or a.surname CONTAINS "son"
  • A CASE expression.

3.2.2.2. Note on string literals

String literals can contain these escape sequences.

Escape sequence Character

\t

Tab

\b

Backspace

\n

Newline

\r

Carriage return

\f

Form feed

\'

Single quote

\"

Double quote

\\

Backslash

\uxxxx

Unicode UTF-16 code point (4 hex digits must follow the \u)

\Uxxxxxxxx

Unicode UTF-32 code point (8 hex digits must follow the \U)

3.2.2.3. Case Expressions

Cypher supports CASE expressions, which is a generic conditional expression, similar to if/else statements in other languages. Two variants of CASE exist — the simple form and the generic form.

Simple CASE

The expression is calculated, and compared in order with the WHEN clauses until a match is found. If no match is found the expression in the ELSE clause is used, or null, if no ELSE case exists.

Syntax:

CASE test
WHEN value THEN result
[WHEN ...]
[ELSE default]
END

Arguments:

  • test: A valid expression.
  • value: An expression whose result will be compared to the test expression.
  • result: This is the result expression used if the value expression matches the test expression.
  • default: The expression to use if no match is found.

Query. 

MATCH (n)
RETURN
CASE n.eyes
WHEN 'blue'
THEN 1
WHEN 'brown'
THEN 2
ELSE 3 END AS result

Result. 

+--------+
| result |
+--------+
| 2      |
| 1      |
| 3      |
| 2      |
| 1      |
+--------+
5 rows

Generic CASE

The predicates are evaluated in order until a true value is found, and the result value is used. If no match is found the expression in the ELSE clause is used, or null, if no ELSE case exists.

Syntax:

CASE
WHEN predicate THEN result
[WHEN ...]
[ELSE default]
END

Arguments:

  • predicate: A predicate that is tested to find a valid alternative.
  • result: This is the result expression used if the predicate matches.
  • default: The expression to use if no match is found.

Query. 

MATCH (n)
RETURN
CASE
WHEN n.eyes = 'blue'
THEN 1
WHEN n.age < 40
THEN 2
ELSE 3 END AS result

Result. 

+--------+
| result |
+--------+
| 2      |
| 1      |
| 3      |
| 3      |
| 1      |
+--------+
5 rows

3.2.3. Variables

When you reference parts of a pattern or a query, you do so by naming them. The names you give the different parts are called variables.

In this example:

MATCH (n)-->(b) RETURN b

The variables are n and b.

Variable names are case sensitive, and can contain underscores and alphanumeric characters (a-z, 0-9), but must always start with a letter. If other characters are needed, you can quote the variable using backquote (`) signs.

The same rules apply to property names.

Variables are not carried over to subsequent queries. If multiple query parts are chained together using WITH, variables have to be listed in the WITH clause to be carried over to the next part. For more information see Section 3.3.20, “WITH”.

3.2.4. Parameters

Cypher supports querying with parameters. This means developers don’t have to resort to string building to create a query. In addition to that, it also makes caching of execution plans much easier for Cypher.

Parameters can be used for literals and expressions in the WHERE clause, for the index value in the START clause, index queries, and finally for node/relationship ids. Parameters can not be used as for property names, relationship types and labels, since these patterns are part of the query structure that is compiled into a query plan.

Accepted names for parameters are letters and numbers, and any combination of these.

Below follows a comprehensive set of examples of parameter usage. The parameters are given as JSON here. Exactly how to submit them depends on the driver in use.

3.2.4.1. String literal

Parameters. 

{
  "name" : "Johan"
}

Query. 

MATCH (n)
WHERE n.name = { name }
RETURN n

You can use parameters in this syntax as well:

Parameters. 

{
  "name" : "Johan"
}

Query. 

MATCH (n { name: { name }})
RETURN n

3.2.4.2. Regular expression

Parameters. 

{
  "regex" : ".*h.*"
}

Query. 

MATCH (n)
WHERE n.name =~ { regex }
RETURN n.name

3.2.4.3. Case-sensitive string pattern matching

Parameters. 

{
  "name" : "Michael"
}

Query. 

MATCH (n)
WHERE n.name STARTS WITH { name }
RETURN n.name

3.2.4.4. Create node with properties

Parameters. 

{
  "props" : {
    "name" : "Andres",
    "position" : "Developer"
  }
}

Query. 

CREATE ({ props })

3.2.4.5. Create multiple nodes with properties

Parameters. 

{
  "props" : [ {
    "awesome" : true,
    "name" : "Andres",
    "position" : "Developer"
  }, {
    "children" : 3,
    "name" : "Michael",
    "position" : "Developer"
  } ]
}

Query. 

UNWIND { props } AS properties
CREATE (n:Person)
SET n = properties
RETURN n

3.2.4.6. Setting all properties on node

Note that this will replace all the current properties.

Parameters. 

{
  "props" : {
    "name" : "Andres",
    "position" : "Developer"
  }
}

Query. 

MATCH (n)
WHERE n.name='Michaela'
SET n = { props }

3.2.4.7. SKIP and LIMIT

Parameters. 

{
  "s" : 1,
  "l" : 1
}

Query. 

MATCH (n)
RETURN n.name
SKIP { s }
LIMIT { l }

3.2.4.8. Node id

Parameters. 

{
  "id" : 0
}

Query. 

MATCH (n)
WHERE id(n)= { id }
RETURN n.name

3.2.4.9. Multiple node ids

Parameters. 

{
  "ids" : [ 0, 1, 2 ]
}

Query. 

MATCH (n)
WHERE id(n) IN { ids }
RETURN n.name

3.2.4.10. Index value (legacy indexes)

Parameters. 

{
  "value" : "Michaela"
}

Query. 

START n=node:people(name = { value })
RETURN n

3.2.4.11. Index query (legacy indexes)

Parameters. 

{
  "query" : "name:Andreas"
}

Query. 

START n=node:people({ query })
RETURN n

3.2.5. Operators

3.2.5.1. Mathematical operators

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

3.2.5.2. Comparison operators

The comparison operators are =, <>, <, >, , >=, IS NULL, and IS NOT NULL. See Section 3.2.5.7, “Equality and Comparison of Values” on how they behave.

The operators STARTS WITH, ENDS WITH and CONTAINS can be used to search for a string value by its content.

3.2.5.3. Boolean operators

The boolean operators are AND, OR, XOR, NOT.

3.2.5.4. String operators

Strings can be concatenated using the + operator. For regular expression matching the =~ operator is used.

3.2.5.5. List operators

Lists can be concatenated using the + operator. To check if an element exists in a list, you can use the IN operator.

3.2.5.6. Property operators

Since version 2.0, the previously existing property operators ? and ! have been removed. This syntax is no longer supported. Missing properties are now returned as NULL. Please use (NOT(has(<ident>.prop)) OR <ident>.prop=<value>) if you really need the old behavior of the ? operator. — Also, the use of ? for optional relationships has been removed in favor of the newly introduced OPTIONAL MATCH clause.

3.2.5.7. Equality and Comparison of Values

Equality

Cypher supports comparing values (see Section 3.2.1, “Values”) by equality using the = and <> operators.

Values of the same type are only equal if they are the same identical value (e.g. 3 = 3 and "x" <> "xy").

Maps are only equal if they map exactly the same keys to equal values and lists are only equal if they contain the same sequence of equal values (e.g. [3, 4] = [1+2, 8/2]).

Values of different types are considered as equal according to the following rules:

  • Paths are treated as lists of alternating nodes and relationships and are equal to all lists that contain that very same sequence of nodes and relationships.
  • Testing any value against NULL with both the = and the <> operators always is NULL. This includes NULL = NULL and NULL <> NULL. The only way to reliably test if a value v is NULL is by using the special v IS NULL, or v IS NOT NULL equality operators.

All other combinations of types of values cannot be compared with each other. Especially, nodes, relationships, and literal maps are incomparable with each other.

It is an error to compare values that cannot be compared.

3.2.5.8. Ordering and Comparison of Values

The comparison operators , < (for ascending) and >=, > (for descending) are used to compare values for ordering. The following points give some details on how the comparison is performed.

  • Numerical values are compared for ordering using numerical order (e.g. 3 < 4 is true).
  • The special value java.lang.Double.NaN is regarded as being larger than all other numbers.
  • String values are compared for ordering using lexicographic order (e.g. "x" < "xy").
  • Boolean values are compared for ordering such that false < true.
  • Comparing for ordering when one argument is NULL is NULL (e.g. NULL < 3 is NULL).
  • It is an error to compare other types of values with each other for ordering.

3.2.5.9. Chaining Comparison Operations

Comparisons can be chained arbitrarily, e.g., x < y ⇐ z is equivalent to x < y AND y ⇐ z.

Formally, if a, b, c, …​, y, z are expressions and op1, op2, …​, opN are comparison operators, then a op1 b op2 c …​ y opN z is equivalent to a op1 b and b op2 c and …​ y opN z.

Note that a op1 b op2 c does not imply any kind of comparison between a and c, so that, e.g., x < y > z is perfectly legal (though perhaps not pretty).

The example:

MATCH (n) WHERE 21 < n.age <= 30 RETURN n

is equivalent to

MATCH (n) WHERE 21 < n.age AND n.age <= 30 RETURN n

Thus it will match all nodes where the age is between 21 and 30.

This syntax extends to all equality and inequality comparisons, as well as extending to chains longer than three.

For example:

a < b = c <= d <> e

Is equivalent to:

a < b AND b = c AND c <= d AND d <> e

For other comparison operators, see Section 3.2.5.2, “Comparison operators”.

3.2.6. Comments

To add comments to your queries, use double slash. Examples:

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

3.2.7. Patterns

Patterns and pattern-matching are at the very heart of Cypher, so being effective with Cypher requires a good understanding of patterns.

Using patterns, you describe the shape of the data you’re looking for. For example, in the MATCH clause you describe the shape with a pattern, and Cypher will figure out how to get that data for you.

The pattern describes the data using a form that is very similar to how one typically draws the shape of property graph data on a whiteboard: usually as circles (representing nodes) and arrows between them to represent relationships.

Patterns appear in multiple places in Cypher: in MATCH, CREATE and MERGE clauses, and in pattern expressions. Each of these is described in more details in:

3.2.7.1. Patterns for nodes

The very simplest ``shape'' that can be described in a pattern is a node. A node is described using a pair of parentheses, and is typically given a name. For example:

(a)

This simple pattern describes a single node, and names that node using the variable a.

3.2.7.3. Labels

In addition to simply describing the shape of a node in the pattern, one can also describe attributes. The most simple attribute that can be described in the pattern is a label that the node must have. For example:

(a:User)-->(b)

One can also describe a node that has multiple labels:

(a:User:Admin)-->(b)

3.2.7.4. Specifying properties

Nodes and relationships are the fundamental structures in a graph. Neo4j uses properties on both of these to allow for far richer models.

Properties can be expressed in patterns using a map-construct: curly brackets surrounding a number of key-expression pairs, separated by commas. E.g. a node with two properties on it would look like:

(a { name: "Andres", sport: "Brazilian Ju-Jitsu" })

A relationship with expectations on it would could look like:

(a)-[{blocked: false}]->(b)

When properties appear in patterns, they add an additional constraint to the shape of the data. In the case of a CREATE clause, the properties will be set in the newly created nodes and relationships. In the case of a MERGE clause, the properties will be used as additional constraints on the shape any existing data must have (the specified properties must exactly match any existing data in the graph). If no matching data is found, then MERGE behaves like CREATE and the properties will be set in the newly created nodes and relationships.

Note that patterns supplied to CREATE may use a single parameter to specify properties, e.g: CREATE (node {paramName}). This is not possible with patterns used in other clauses, as Cypher needs to know the property names at the time the query is compiled, so that matching can be done effectively.

3.2.7.5. Describing relationships

The simplest way to describe a relationship is by using the arrow between two nodes, as in the previous examples. Using this technique, you can describe that the relationship should exist and the directionality of it. If you don’t care about the direction of the relationship, the arrow head can be omitted, like so:

(a)--(b)

As with nodes, relationships may also be given names. In this case, a pair of square brackets is used to break up the arrow and the variable is placed between. For example:

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

Much like labels on nodes, relationships can have types. To describe a relationship with a specific type, you can specify this like so:

(a)-[r:REL_TYPE]->(b)

Unlike labels, relationships can only have one type. But if we’d like to describe some data such that the relationship could have any one of a set of types, then they can all be listed in the pattern, separating them with the pipe symbol | like this:

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

Note that this form of pattern can only be used to describe existing data (ie. when using a pattern with MATCH or as an expression). It will not work with CREATE or MERGE, since it’s not possible to create a relationship with multiple types.

As with nodes, the name of the relationship can always be omitted, in this case like so:

(a)-[:REL_TYPE]->(b)
Variable length

Variable length pattern matching in versions 2.1.x and earlier does not enforce relationship uniqueness for patterns described inside of a single MATCH clause. This means that a query such as the following: MATCH (a)-[r]→(b), (a)-[rs*]→(c) RETURN * may include r as part of the rs set. This behavior has changed in versions 2.2.0 and later, in such a way that r will be excluded from the result set, as this better adheres to the rules of relationship uniqueness as documented here Section 3.1.4, “Uniqueness”. If you have a query pattern that needs to retrace relationships rather than ignoring them as the relationship uniqueness rules normally dictate, you can accomplish this using multiple match clauses, as follows: MATCH (a)-[r]→(b) MATCH (a)-[rs*]→(c) RETURN *. This will work in all versions of Neo4j that support the MATCH clause, namely 2.0.0 and later.

Rather than describing a long path using a sequence of many node and relationship descriptions in a pattern, many relationships (and the intermediate nodes) can be described by specifying a length in the relationship description of a pattern. For example:

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

This describes a graph of three nodes and two relationship, all in one path (a path of length 2). This is equivalent to:

(a)-->()-->(b)

A range of lengths can also be specified: such relationship patterns are called ``variable length relationships''. For example:

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

This is a minimum length of 3, and a maximum of 5. It describes a graph of either 4 nodes and 3 relationships, 5 nodes and 4 relationships or 6 nodes and 5 relationships, all connected together in a single path.

Either bound can be omitted. For example, to describe paths of length 3 or more, use:

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

And to describe paths of length 5 or less, use:

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

Both bounds can be omitted, allowing paths of any length to be described:

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

As a simple example, let’s take the query below:

Query. 

MATCH (me)-[:KNOWS*1..2]-(remote_friend)
WHERE me.name = "Filipa"
RETURN remote_friend.name

Result. 

+--------------------+
| remote_friend.name |
+--------------------+
| "Dilshad"          |
| "Anders"           |
+--------------------+
2 rows

This query finds data in the graph which a shape that fits the pattern: specifically a node (with the name property Filipa) and then the KNOWS related nodes, one or two steps out. This is a typical example of finding first and second degree friends.

Note that variable length relationships can not be used with CREATE and MERGE.

3.2.7.6. Assigning to path variables

As described above, a series of connected nodes and relationships is called a "path". Cypher allows paths to be named using an identifer, like so:

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

You can do this in MATCH, CREATE and MERGE, but not when using patterns as expressions.

3.2.8. Lists

Cypher has good support for lists.

3.2.8.1. Lists in general

A literal list is created by using brackets and separating the elements in the list with commas.

Query. 

RETURN [0,1,2,3,4,5,6,7,8,9] AS list

Result. 

+-----------------------+
| list                  |
+-----------------------+
| [0,1,2,3,4,5,6,7,8,9] |
+-----------------------+
1 row

In our examples, we’ll use the range function. It gives you a list containing all numbers between given start and end numbers. Range is inclusive in both ends.

To access individual elements in the list, we use the square brackets again. This will extract from the start index and up to but not including the end index.

Query. 

RETURN range(0,10)[3]

Result. 

+----------------+
| range(0,10)[3] |
+----------------+
| 3              |
+----------------+
1 row

You can also use negative numbers, to start from the end of the list instead.

Query. 

RETURN range(0,10)[-3]

Result. 

+-----------------+
| range(0,10)[-3] |
+-----------------+
| 8               |
+-----------------+
1 row

Finally, you can use ranges inside the brackets to return ranges of the list.

Query. 

RETURN range(0,10)[0..3]

Result. 

+-------------------+
| range(0,10)[0..3] |
+-------------------+
| [0,1,2]           |
+-------------------+
1 row

Query. 

RETURN range(0,10)[0..-5]

Result. 

+--------------------+
| range(0,10)[0..-5] |
+--------------------+
| [0,1,2,3,4,5]      |
+--------------------+
1 row

Query. 

RETURN range(0,10)[-5..]

Result. 

+-------------------+
| range(0,10)[-5..] |
+-------------------+
| [6,7,8,9,10]      |
+-------------------+
1 row

Query. 

RETURN range(0,10)[..4]

Result. 

+------------------+
| range(0,10)[..4] |
+------------------+
| [0,1,2,3]        |
+------------------+
1 row

Out-of-bound slices are simply truncated, but out-of-bound single elements return NULL.

Query. 

RETURN range(0,10)[15]

Result. 

+-----------------+
| range(0,10)[15] |
+-----------------+
| <null>          |
+-----------------+
1 row

Query. 

RETURN range(0,10)[5..15]

Result. 

+--------------------+
| range(0,10)[5..15] |
+--------------------+
| [5,6,7,8,9,10]     |
+--------------------+
1 row

You can get the size of a list like this:

Query. 

RETURN size(range(0,10)[0..3])

Result. 

+-------------------------+
| size(range(0,10)[0..3]) |
+-------------------------+
| 3                       |
+-------------------------+
1 row

3.2.8.2. List comprehension

List comprehension is a syntactic construct available in Cypher for creating a list based on existing lists. It follows the form of the mathematical set-builder notation (set comprehension) instead of the use of map and filter functions.

Query. 

RETURN [x IN range(0,10) WHERE x % 2 = 0 | x^3] AS result

Result. 

+-----------------------------------+
| result                            |
+-----------------------------------+
| [0.0,8.0,64.0,216.0,512.0,1000.0] |
+-----------------------------------+
1 row

Either the WHERE part, or the expression, can be omitted, if you only want to filter or map respectively.

Query. 

RETURN [x IN range(0,10) WHERE x % 2 = 0] AS result

Result. 

+----------------+
| result         |
+----------------+
| [0,2,4,6,8,10] |
+----------------+
1 row

Query. 

RETURN [x IN range(0,10)| x^3] AS result

Result. 

+--------------------------------------------------------------+
| result                                                       |
+--------------------------------------------------------------+
| [0.0,1.0,8.0,27.0,64.0,125.0,216.0,343.0,512.0,729.0,1000.0] |
+--------------------------------------------------------------+
1 row

3.2.8.3. Literal maps

From Cypher, you can also construct maps. Through REST you will get JSON objects; in Java they will be java.util.Map<String,Object>.

Query. 

RETURN { key : "Value", listKey: [{ inner: "Map1" }, { inner: "Map2" }]} AS result

Result. 

+--------------------------------------------------------------------+
| result                                                             |
+--------------------------------------------------------------------+
| {key -> "Value", listKey -> [{inner -> "Map1"},{inner -> "Map2"}]} |
+--------------------------------------------------------------------+
1 row

3.2.9. Working with NULL

3.2.9.1. Introduction to NULL in Cypher

In Cypher, NULL is used to represent missing or undefined values. Conceptually, NULL means ``a missing unknown value'' and it is treated somewhat differently from other values. For example getting a property from a node that does not have said property produces NULL. Most expressions that take NULL as input will produce NULL. This includes boolean expressions that are used as predicates in the WHERE clause. In this case, anything that is not TRUE is interpreted as being false.

NULL is not equal to NULL. Not knowing two values does not imply that they are the same value. So the expression NULL = NULL yields NULL and not TRUE.

3.2.9.2. Logical operations with NULL

The logical operators (AND, OR, XOR, IN, NOT) treat NULL as the ``unknown'' value of three-valued logic. Here is the truth table for AND, OR and XOR.

a b a AND b a OR b a XOR b

FALSE

FALSE

FALSE

FALSE

FALSE

FALSE

NULL

FALSE

NULL

NULL

FALSE

TRUE

FALSE

TRUE

TRUE

TRUE

FALSE

FALSE

TRUE

TRUE

TRUE

NULL

NULL

TRUE

NULL

TRUE

TRUE

TRUE

TRUE

FALSE

NULL

FALSE

FALSE

NULL

NULL

NULL

NULL

NULL

NULL

NULL

NULL

TRUE

NULL

TRUE

NULL

3.2.9.3. The IN operator and NULL

The IN operator follows similar logic. If Cypher knows that something exists in a list, the result will be true. Any list that contains a NULL and doesn’t have a matching element will return NULL. Otherwise, the result will be false. Here is a table with examples:

Expression Result

2 IN [1, 2, 3]

TRUE

2 IN [1, NULL, 3]

NULL

2 IN [1, 2, NULL]

TRUE

2 IN [1]

FALSE

2 IN []

FALSE

NULL IN [1,2,3]

NULL

NULL IN [1,NULL,3]

NULL

NULL IN []

FALSE

Using ALL, ANY, NONE, and SINGLE follows a similar rule. If the result can be calculated definitely, TRUE or FALSE is returned. Otherwise NULL is produced.

3.2.9.4. Expressions that return NULL

  • Getting a missing element from a list: [][0], head([])
  • Trying to access a property that does not exist on a node or relationship: n.missingProperty
  • Comparisons when either side is NULL: `1 < NULL`
  • Arithmetic expressions containing NULL: `1 + NULL`
  • Function calls where any arguments are NULL: sin(NULL)

3.3. Clauses

Read Clauses

These comprise clauses that read data from the database.

The flow of data within a Cypher query is an unordered sequence of maps with key-value pairs — a set of possible bindings between the variables in the query and values derived from the database. This set is refined and augmented by subsequent parts of the query.

Clause Description

MATCH

Specify the patterns to search for in the database.

OPTIONAL MATCH

Specify the patterns to search for in the database while using NULLs for missing parts of the pattern.

WHERE

Adds constraints to the patterns in a MATCH or OPTIONAL MATCH clause or filter the results of a WITH clause.

START

Find starting points through legacy indexes.

Aggregation

Aggregation functions including count(), sum(), avg(), max(), min(), collect() and others. Includes DISTINCT.

LOAD CSV

Use when importing data from CSV files.

Write Clauses

These comprise clauses that write the data to the database.

Clause Description

CREATE

Create nodes and relationships.

MERGE

Ensures that a pattern exists in the graph. Either the pattern already exists, or it needs to be created.

SET

Update labels on nodes and properties on nodes and relationships.

DELETE

Delete graph elements — nodes, relationships or paths.

REMOVE

Remove properties and labels from nodes and relationships.

FOREACH

Update data within a list, whether components of a path, or the result of aggregation.

CREATE UNIQUE

A mixture of MATCH and CREATE, matching what it can, and creating what is missing.

Importing CSV files with Cypher

How to import data from CSV files using LOAD CSV.

PERIODIC COMMIT

How and when to use PERIODIC COMMIT

General Clauses

These comprise general clauses that work in conjunction with other clauses.

Clause Description

RETURN

Defines what to include in the query result set.

ORDER BY

A sub-clause following RETURN or WITH, specifying that the output should be sorted in particular way.

LIMIT

Constrains the number of rows in the output.

SKIP

Defines from which row to start including the rows in the output.

WITH

Allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next.

UNWIND

Expands a list into a sequence of rows.

UNION

Combines the result of multiple queries.

CALL

Invoke a procedure deployed in the database.

3.3.1. MATCH

The MATCH clause is used to search for the pattern described in it.

3.3.1.1. Introduction

The MATCH clause allows you to specify the patterns Neo4j will search for in the database. This is the primary way of getting data into the current set of bindings. It is worth reading up more on the specification of the patterns themselves in Section 3.2.7, “Patterns”.

MATCH is often coupled to a WHERE part which adds restrictions, or predicates, to the MATCH patterns, making them more specific. The predicates are part of the pattern description, and should not be considered a filter applied only after the matching is done. This means that WHERE should always be put together with the MATCH clause it belongs to.

MATCH can occur at the beginning of the query or later, possibly after a WITH. If it is the first clause, nothing will have been bound yet, and Neo4j will design a search to find the results matching the clause and any associated predicates specified in any WHERE part. This could involve a scan of the database, a search for nodes of a certain label, or a search of an index to find starting points for the pattern matching. Nodes and relationships found by this search are available as bound pattern elements, and can be used for pattern matching of sub-graphs. They can also be used in any further MATCH clauses, where Neo4j will use the known elements, and from there find further unknown elements.

Cypher is declarative, and so usually the query itself does not specify the algorithm to use to perform the search. Neo4j will automatically work out the best approach to finding start nodes and matching patterns. Predicates in WHERE parts can be evaluated before pattern matching, during pattern matching, or after finding matches. However, there are cases where you can influence the decisions taken by the query compiler. Read more about indexes in Section 3.5.1, “Indexes”, and more about specifying hints to force Neo4j to solve a query in a specific way in Section 3.6.4, “USING”.

To understand more about the patterns used in the MATCH clause, read Section 3.2.7, “Patterns”

The following graph is used for the examples below:

Figure 3.2. Graph
alt

3.3.1.2. Basic node finding

Get all nodes

By just specifying a pattern with a single node and no labels, all nodes in the graph will be returned.

Query. 

MATCH (n)
RETURN n

Returns all the nodes in the database.

Table 3.1. Result
n

7 rows

Node[0]\{name:"Charlie Sheen"\}

Node[1]\{name:"Martin Sheen"\}

Node[2]\{name:"Michael Douglas"\}

Node[3]\{name:"Oliver Stone"\}

Node[4]\{name:"Rob Reiner"\}

Node[5]\{title:"Wall Street"\}

Node[6]\{title:"The American President"\}

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (n) RETURN n

Get all nodes with a label

Getting all nodes with a label on them is done with a single node pattern where the node has a label on it.

Query. 

MATCH (movie:Movie)
RETURN movie.title

Returns all the movies in the database.

Table 3.2. Result
movie.title

2 rows

"Wall Street"

"The American President"

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (movie:Movie) RETURN movie.title

Match with labels

To constrain your pattern with labels on nodes, you add it to your pattern nodes, using the label syntax.

Query. 

MATCH (:Person { name:'Oliver Stone' })--(movie:Movie)
RETURN movie.title

Returns any nodes connected with the Person Oliver that are labeled Movie.

Table 3.4. Result
movie.title

1 row

"Wall Street"

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (:Person { name:'Oliver Stone' })--(movie:Movie) RETURN movie.title

3.3.1.3. Relationship basics

Outgoing relationships

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

Query. 

MATCH (:Person { name:'Oliver Stone' })-->(movie)
RETURN movie.title

Returns any nodes connected with the Person Oliver by an outgoing relationship.

Table 3.5. Result
movie.title

1 row

"Wall Street"

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (:Person { name:'Oliver Stone' })-->(movie) RETURN movie.title

Directed relationships and variable

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

Query. 

MATCH (:Person { name:'Oliver Stone' })-[r]->(movie)
RETURN type(r)

Returns the type of each outgoing relationship from Oliver.

Table 3.6. Result
type(r)

1 row

"DIRECTED"

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (:Person { name:'Oliver Stone' })-[r]->(movie) RETURN type(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. 

MATCH (wallstreet:Movie { title:'Wall Street' })<-[:ACTED_IN]-(actor)
RETURN actor.name

Returns all actors that ACTED_IN Wall Street.

Table 3.7. Result
actor.name

3 rows

"Michael Douglas"

"Martin Sheen"

"Charlie Sheen"

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (wallstreet:Movie { title:'Wall Street' })<-[:ACTED_IN]-(actor) RETURN actor.name

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. 

MATCH (wallstreet { title:'Wall Street' })<-[:ACTED_IN|:DIRECTED]-(person)
RETURN person.name

Returns nodes with an ACTED_IN or DIRECTED relationship to Wall Street.

Table 3.8. Result
person.name

4 rows

"Oliver Stone"

"Michael Douglas"

"Martin Sheen"

"Charlie Sheen"

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (wallstreet { title:'Wall Street' })<-[:ACTED_IN|:DIRECTED]-(person) RETURN person.name

Match by relationship type and use an variable

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

Query. 

MATCH (wallstreet { title:'Wall Street' })<-[r:ACTED_IN]-(actor)
RETURN r.role

Returns ACTED_IN roles for Wall Street.

Table 3.9. Result
r.role

3 rows

"Gordon Gekko"

"Carl Fox"

"Bud Fox"

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (wallstreet { title:'Wall Street' })<-[r:ACTED_IN]-(actor) RETURN r.role

3.3.1.4. Relationships in depth

Inside a single pattern, relationships will only be matched once. You can read more about this in Section 3.1.4, “Uniqueness”.

Relationship types with uncommon characters

Sometimes your database will have types with non-letter characters, or with spaces in them. Use ` (backtick) to quote these. To demonstrate this we can add an additional relationship between Charlie Sheen and Rob Reiner:

Query. 

MATCH (charlie:Person { name:'Charlie Sheen' }),(rob:Person { name:'Rob Reiner' })
CREATE (rob)-[:`TYPE
WITH SPACE`]->(charlie)

Which leads to the following graph:

Figure 3.3. Graph
alt

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (charlie:Person {name:'Charlie Sheen'}), (rob:Person {name:'Rob Reiner'}) CREATE (rob)-[:`TYPE WITH SPACE`]->(charlie)

Query. 

MATCH (n { name:'Rob Reiner' })-[r:`TYPE
WITH SPACE`]->()
RETURN type(r)

Returns a relationship type with a space in it

Table 3.10. Result
type(r)

1 row

"TYPE WITH SPACE"

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (charlie:Person {name:'Charlie Sheen'}), (rob:Person {name:'Rob Reiner'}) CREATE (rob)-[:`TYPE WITH SPACE`]->(charlie) MATCH (n { name:'Rob Reiner' })-[r:`TYPE WITH SPACE`]->() RETURN type(r)

Multiple relationships

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

Query. 

MATCH (charlie { name:'Charlie Sheen' })-[:ACTED_IN]->(movie)<-[:DIRECTED]-(director)
RETURN movie.title, director.name

Returns the movie Charlie acted in and its director.

Table 3.11. Result
movie.title director.name

1 row

"Wall Street"

"Oliver Stone"

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) match (charlie {name:'Charlie Sheen'})-[:ACTED_IN]->(movie)<-[:DIRECTED]-(director) return movie.title, director.name

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. The dots may also be omitted when setting only one bound and this implies a fixed length pattern.

Query. 

MATCH (martin { name:'Charlie Sheen' })-[:ACTED_IN*1..3]-(movie:Movie)
RETURN movie.title

Returns all movies related to Charlie by 1 to 3 hops.

Table 3.12. Result
movie.title

3 rows

"Wall Street"

"The American President"

"The American President"

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) match (martin {name:'Charlie Sheen'})-[:ACTED_IN*1..3]-(movie:Movie) return movie.title

Relationship variable in variable length relationships

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

Query. 

MATCH (actor { name:'Charlie Sheen' })-[r:ACTED_IN*2]-(co_actor)
RETURN r

Returns a list of relationships.

Table 3.13. Result
r

2 rows

[:ACTED_IN[0]\{role:"Bud Fox"\},:ACTED_IN[1]\{role:"Carl Fox"\}]

[:ACTED_IN[0]\{role:"Bud Fox"\},:ACTED_IN[2]\{role:"Gordon Gekko"\}]

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (actor { name:'Charlie Sheen' })-[r:ACTED_IN*2]-(co_actor) RETURN r

Match with properties on a variable length path

A variable length relationship with properties defined on in it means that all relationships in the path must have the property set to the given value. In this query, there are two paths between Charlie Sheen and his father Martin Sheen. One of them includes a blocked'' relationship and the other doesn’t. In this case we first alter the original graph by using the following query to add blocked'' and ``unblocked'' relationships:

Query. 

MATCH (charlie:Person { name:'Charlie Sheen' }),(martin:Person { name:'Martin Sheen' })
CREATE (charlie)-[:X { blocked:false }]->(:Unblocked)<-[:X { blocked:false }]-(martin)
CREATE (charlie)-[:X { blocked:true }]->(:Blocked)<-[:X { blocked:false }]-(martin)

This means that we are starting out with the following graph:

Figure 3.4. Graph
alt

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name:'Martin Sheen'}) CREATE (charlie)-[:X {blocked:false}]->(:Unblocked)<-[:X {blocked:false}]-(martin) CREATE (charlie)-[:X {blocked:true}]->(:Blocked)<-[:X {blocked:false}]-(martin)

Query. 

MATCH p =(charlie:Person)-[* { blocked:false }]-(martin:Person)
WHERE charlie.name = 'Charlie Sheen' AND martin.name = 'Martin Sheen'
RETURN p

Returns the paths between Charlie and Martin Sheen where all relationships have the blocked property set to FALSE.

Table 3.14. Result
p

1 row

[Node[0]\{name:"Charlie Sheen"\},:X[7]\{blocked:false\},Node[7]\{\},:X[8]\{blocked:false\},Node[1]\{name:"Martin Sheen"\}]

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name:'Martin Sheen'}) CREATE (charlie)-[:X {blocked:false}]->(:Unblocked)<-[:X {blocked:false}]-(martin) CREATE (charlie)-[:X {blocked:true}]->(:Blocked)<-[:X {blocked:false}]-(martin) MATCH p = (charlie:Person)-[* {blocked:false}]-(martin:Person) WHERE charlie.name = 'Charlie Sheen' AND martin.name = 'Martin Sheen' RETURN p

Zero length paths

Using variable length paths that have the lower bound zero means that two variables can point to the same node. If the path length 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. 

MATCH (wallstreet:Movie { title:'Wall Street' })-[*0..1]-(x)
RETURN x

Returns the movie itself as well as actors and directors one relationship away

Table 3.15. Result
x

5 rows

Node[5]\{title:"Wall Street"\}

Node[0]\{name:"Charlie Sheen"\}

Node[1]\{name:"Martin Sheen"\}

Node[2]\{name:"Michael Douglas"\}

Node[3]\{name:"Oliver Stone"\}

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (wallstreet:Movie { title:'Wall Street' })-[*0..1]-(x) RETURN x

Named path

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

Query. 

MATCH p =(michael { name:'Michael Douglas' })-->()
RETURN p

Returns the two paths starting from Michael

Table 3.16. Result
p

2 rows

[Node[2]\{name:"Michael Douglas"\},:ACTED_IN[5]\{role:"President Andrew Shepherd"\},Node[6]\{title:"The American President"\}]

[Node[2]\{name:"Michael Douglas"\},:ACTED_IN[2]\{role:"Gordon Gekko"\},Node[5]\{title:"Wall Street"\}]

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH p =(michael { name:'Michael Douglas' })-->() 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 in both directions.

Query. 

MATCH (a)-[r]-(b)
WHERE id(r)= 0
RETURN a,b

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

Table 3.17. Result
a b

2 rows

Node[0]\{name:"Charlie Sheen"\}

Node[5]\{title:"Wall Street"\}

Node[5]\{title:"Wall Street"\}

Node[0]\{name:"Charlie Sheen"\}

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (a)-[r]-(b) WHERE id(r)= 0 RETURN a,b

3.3.1.5. Shortest path

Single shortest path

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

Query. 

MATCH (martin:Person { name:"Martin Sheen" }),(oliver:Person { name:"Oliver Stone" }), p = shortestPath((martin)-[*..15]-(oliver))
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 parentheses 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. If there is a WHERE clause following the match of a shortestPath, relevant predicates will be included in the shortestPath. If the predicate is a NONE() or ALL() on the relationship elements of the path, it will be used during the search to improve performance (see Section 3.7.6, “Shortest path planning”).

Table 3.18. Result
p

1 row

[Node[1]\{name:"Martin Sheen"\},:ACTED_IN[1]\{role:"Carl Fox"\},Node[5]\{title:"Wall Street"\},:DIRECTED[3]\{\},Node[3]\{name:"Oliver Stone"\}]

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (martin:Person {name:"Martin Sheen"} ), (oliver:Person {name:"Oliver Stone"}), p = shortestPath( (martin)-[*..15]-(oliver) ) RETURN p

Single shortest path with predicates

Predicates used in the WHERE clause that apply to the shortest path pattern are evaluated before deciding what the shortest matching path is.

Query. 

MATCH (charlie:Person { name:"Charlie Sheen" }),(martin:Person { name:"Martin Sheen" }), p = shortestPath((charlie)-[*]-(martin))
WHERE NONE (r IN rels(p) WHERE type(r)= "FATHER")
RETURN p

This query will find the shortest path between 'Charlie Sheen' and 'Martin Sheen', and the WHERE predicate will ensure that we don’t consider the father/son relationship between the two.

Table 3.19. Result
p

1 row

[Node[0]\{name:"Charlie Sheen"\},:ACTED_IN[0]\{role:"Bud Fox"\},Node[5]\{title:"Wall Street"\},:ACTED_IN[1]\{role:"Carl Fox"\},Node[1]\{name:"Martin Sheen"\}]

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) MATCH (charlie:Person {name:"Charlie Sheen"}), (martin:Person {name:"Martin Sheen"}), p = shortestPath( (charlie)-[*]-(martin) ) WHERE NONE(r in rels(p) WHERE type(r) = "FATHER") RETURN p

All shortest paths

Finds all the shortest paths between two nodes.

Query. 

MATCH (martin:Person { name:"Martin Sheen" }),(michael:Person { name:"Michael Douglas" }), p = allShortestPaths((martin)-[*]-(michael))
RETURN p

Finds the two shortest paths between 'Martin' and 'Michael'.

Table 3.20. Result
p

2 rows

[Node[1]\{name:"Martin Sheen"\},:ACTED_IN[1]\{role:"Carl Fox"\},Node[5]\{title:"Wall Street"\},:ACTED_IN[2]\{role:"Gordon Gekko"\},Node[2]\{name:"Michael Douglas"\}]

[Node[1]\{name:"Martin Sheen"\},:ACTED_IN[4]\{role:"A.J. MacInerney"\},Node[6]\{title:"The American President"\},:ACTED_IN[5]\{role:"President Andrew Shepherd"\},Node[2]\{name:"Michael Douglas"\}]

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) match (martin:Person {name:"Martin Sheen"} ), (michael:Person {name:"Michael Douglas"}), p = allShortestPaths( (martin)-[*]-(michael) ) return p

3.3.1.6. Get node or relationship by id

Node by id

Searching for nodes by id can be done with the id() function in a predicate.

Neo4j reuses its internal ids when nodes and relationships are deleted. This means that applications using, and relying on internal Neo4j ids, are brittle or at risk of making mistakes. It is therefor recommended to rather use application generated ids.

Query. 

MATCH (n)
WHERE id(n)= 0
RETURN n

The corresponding node is returned.

Table 3.21. Result
n

1 row

Node[0]\{name:"Charlie Sheen"\}

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) match (n) where id(n) = 0 return n

Relationship by id

Search for relationships by id can be done with the id() function in a predicate.

This is not recommended practice. See the section called “Node by id” for more information on the use of Neo4j ids.

Query. 

MATCH ()-[r]->()
WHERE id(r)= 0
RETURN r

The relationship with id 0 is returned.

Table 3.22. Result
r

1 row

:ACTED_IN[0]\{role:"Bud Fox"\}

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) match ()-[r]->() where id(r) = 0 return r

Multiple nodes by id

Multiple nodes are selected by specifying them in an IN clause.

Query. 

MATCH (n)
WHERE id(n) IN [0,3,5]
RETURN n

This returns the nodes listed in the IN expression.

Table 3.23. Result
n

3 rows

Node[0]\{name:"Charlie Sheen"\}

Node[3]\{name:"Oliver Stone"\}

Node[5]\{title:"Wall Street"\}

Try this query live.  CREATE (charlie:Person {name:'Charlie Sheen'}), (martin:Person {name: 'Martin Sheen'}), (michael:Person {name: 'Michael Douglas'}), (oliver:Person {name: 'Oliver Stone'}), (rob:Person {name: 'Rob Reiner'}), (wallStreet:Movie {title: 'Wall Street'}), (charlie)-[:ACTED_IN {role: "Bud Fox"}]->(wallStreet), (martin)-[:ACTED_IN {role: "Carl Fox"}]->(wallStreet), (michael)-[:ACTED_IN {role: "Gordon Gekko"}]->(wallStreet), (oliver)-[:DIRECTED]->(wallStreet), (thePresident:Movie {title: 'The American President'}), (martin)-[:ACTED_IN {role: "A.J. MacInerney"}]->(thePresident), (michael)-[:ACTED_IN {role: "President Andrew Shepherd"}]->(thePresident), (rob)-[:DIRECTED]->(thePresident) match (n) where id(n) in [0,3,5] return n

3.3.2. OPTIONAL MATCH

The OPTIONAL MATCH clause is used to search for the pattern described in it, while using NULLs for missing parts of the pattern.

3.3.2.1. Introduction

OPTIONAL MATCH matches patterns against your graph database, just like MATCH does. The difference is that if no matches are found, OPTIONAL MATCH will use NULLs for missing parts of the pattern. OPTIONAL MATCH could be considered the Cypher equivalent of the outer join in SQL.

Either the whole pattern is matched, or nothing is matched. Remember that WHERE is part of the pattern description, and the predicates will be considered while looking for matches, not after. This matters especially in the case of multiple (OPTIONAL) MATCH clauses, where it is crucial to put WHERE together with the MATCH it belongs to.

To understand the patterns used in the OPTIONAL MATCH clause, read Section 3.2.7, “Patterns”.

The following graph is used for the examples below:

Figure 3.5. Graph
alt

3.3.2.2. Relationship

If a relationship is optional, use the OPTIONAL MATCH clause. 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.

Query. 

MATCH (a:Movie { title: 'Wall Street' })
OPTIONAL MATCH (a)-->(x)
RETURN x

Returns NULL, since the node has no outgoing relationships.

Result. 

+--------+
| x      |
+--------+
| <null> |
+--------+
1 row

3.3.2.3. Properties on optional elements

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

Query. 

MATCH (a:Movie { title: 'Wall Street' })
OPTIONAL MATCH (a)-->(x)
RETURN x, x.name

Returns the element x (NULL in this query), and NULL as its name.

Result. 

+-----------------+
| x      | x.name |
+-----------------+
| <null> | <null> |
+-----------------+
1 row

3.3.2.4. Optional typed and named relationship

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

Query. 

MATCH (a:Movie { title: 'Wall Street' })
OPTIONAL MATCH (a)-[r:ACTS_IN]->()
RETURN r

This returns a node, and NULL, since the node has no outgoing ACTS_IN relationships.

Result. 

+--------+
| r      |
+--------+
| <null> |
+--------+
1 row

3.3.3. WHERE

WHERE adds constraints to the patterns in a MATCH or OPTIONAL MATCH clause or filters the results of a WITH clause.

WHERE is not a clause in its own right — rather, it’s part of MATCH, OPTIONAL MATCH, START and WITH.

In the case of WITH and START, WHERE simply filters the results.

For MATCH and OPTIONAL MATCH on the other hand, WHERE adds constraints to the patterns described. It should not be seen as a filter after the matching is finished.

In the case of multiple MATCH / OPTIONAL MATCH clauses, the predicate in WHERE is always a part of the patterns in the directly preceding MATCH / OPTIONAL MATCH. Both results and performance may be impacted if the WHERE is put inside the wrong MATCH clause.

Figure 3.6. Graph
alt

3.3.3.1. Basic usage

Boolean operations

You can use the expected boolean operators AND and OR, and also the boolean function NOT. See Section 3.2.9, “Working with NULL” for more information on how this works with NULL.

Query. 

MATCH (n)
WHERE n.name = 'Peter' XOR (n.age < 30 AND n.name = "Tobias") OR NOT (n.name = "Tobias" OR
  n.name="Peter")
RETURN n

Result. 

+----------------------------------------------------------+
| n                                                        |
+----------------------------------------------------------+
| Node[0]{name:"Andres",age:36,belt:"white"}               |
| Node[1]{address:"Sweden/Malmo",name:"Tobias",age:25}     |
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
3 rows

Filter on node label

To filter nodes by label, write a label predicate after the WHERE keyword using WHERE n:foo.

Query. 

MATCH (n)
WHERE n:Swedish
RETURN n

The "Andres" node will be returned.

Result. 

+--------------------------------------------+
| n                                          |
+--------------------------------------------+
| Node[0]{name:"Andres",age:36,belt:"white"} |
+--------------------------------------------+
1 row

Filter on node property

To filter on a node property, write your clause after the WHERE keyword.

Query. 

MATCH (n)
WHERE n.age < 30
RETURN n

"Tobias" is returned because he is younger than 30.

Result. 

+------------------------------------------------------+
| n                                                    |
+------------------------------------------------------+
| Node[1]{address:"Sweden/Malmo",name:"Tobias",age:25} |
+------------------------------------------------------+
1 row

Filter on relationship property

To filter on a relationship property, write your clause after the WHERE keyword.

Query. 

MATCH (n)-[k:KNOWS]->(f)
WHERE k.since < 2000
RETURN f

"Peter" is returned because Andres knows him since before 2000.

Result. 

+----------------------------------------------------------+
| f                                                        |
+----------------------------------------------------------+
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
1 row

Filter on dynamic node property

To filter on a property using a dynamically computed name, use square bracket syntax.

Parameters. 

{
  "prop" : "AGE"
}

Query. 

MATCH (n)
WHERE n[toLower({ prop })]< 30
RETURN n

"Tobias" is returned because he is younger than 30.

Result. 

+------------------------------------------------------+
| n                                                    |
+------------------------------------------------------+
| Node[1]{address:"Sweden/Malmo",name:"Tobias",age:25} |
+------------------------------------------------------+
1 row

Property exists

Use the EXISTS() function to only include nodes or relationships in which a property exists.

Query. 

MATCH (n)
WHERE exists(n.belt)
RETURN n

"Andres" will be returned because he is the only one with a belt property.

The HAS() function has been superseded by EXISTS() and has been removed.

Result. 

+--------------------------------------------+
| n                                          |
+--------------------------------------------+
| Node[0]{name:"Andres",age:36,belt:"white"} |
+--------------------------------------------+
1 row

3.3.3.2. String matching

The start and end of strings can be matched using STARTS WITH and ENDS WITH. To match regardless of location in a string, use CONTAINS. The matching is case-sensitive.

Match the start of a string

The STARTS WITH operator is used to perform case-sensitive matching on the start of strings.

Query. 

MATCH (n)
WHERE n.name STARTS WITH 'Pet'
RETURN n

"Peter" will be returned because his name starts with Pet.

Result. 

+----------------------------------------------------------+
| n                                                        |
+----------------------------------------------------------+
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
1 row

Match the end of a string

The ENDS WITH operator is used to perform case-sensitive matching on the end of strings.

Query. 

MATCH (n)
WHERE n.name ENDS WITH 'ter'
RETURN n

"Peter" will be returned because his name ends with ter.

Result. 

+----------------------------------------------------------+
| n                                                        |
+----------------------------------------------------------+
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
1 row

Match anywhere in a string

The CONTAINS operator is used to perform case-sensitive matching regardless of location in strings.

Query. 

MATCH (n)
WHERE n.name CONTAINS 'ete'
RETURN n

"Peter" will be returned because his name contains ete.

Result. 

+----------------------------------------------------------+
| n                                                        |
+----------------------------------------------------------+
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
1 row

String matching negation

Use the NOT keyword to exclude all matches on given string from your result:

Query. 

MATCH (n)
WHERE NOT n.name ENDS WITH 's'
RETURN n

"Peter" will be returned because his name does not end with s.

Result. 

+----------------------------------------------------------+
| n                                                        |
+----------------------------------------------------------+
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
1 row

3.3.3.3. Regular expressions

Cypher supports filtering using regular expressions. The regular expression syntax is inherited from the Java regular expressions. This includes support for flags that change how strings are matched, including case-insensitive (?i), multiline (?m) and dotall (?s). Flags are given at the start of the regular expression, for example MATCH (n) WHERE n.name =~ '(?i)Lon.*' RETURN n will return nodes with name London or with name LonDoN.

Regular expressions

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

Query. 

MATCH (n)
WHERE n.name =~ 'Tob.*'
RETURN n

"Tobias" is returned because his name starts with Tob.

Result. 

+------------------------------------------------------+
| n                                                    |
+------------------------------------------------------+
| Node[1]{address:"Sweden/Malmo",name:"Tobias",age:25} |
+------------------------------------------------------+
1 row

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. 

MATCH (n)
WHERE n.address =~ 'Sweden\\/Malmo'
RETURN n

"Tobias" is returned because his address is in Sweden/Malmo.

Result. 

+------------------------------------------------------+
| n                                                    |
+------------------------------------------------------+
| Node[1]{address:"Sweden/Malmo",name:"Tobias",age:25} |
+------------------------------------------------------+
1 row

Case insensitive regular expressions

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

Query. 

MATCH (n)
WHERE n.name =~ '(?i)ANDR.*'
RETURN n

"Andres" is returned because his name starts with ANDR regardless of case.

Result. 

+--------------------------------------------+
| n                                          |
+--------------------------------------------+
| Node[0]{name:"Andres",age:36,belt:"white"} |
+--------------------------------------------+
1 row

3.3.3.4. Using path patterns in WHERE

Filter on patterns

Patterns are expressions in Cypher, expressions that return a list of paths. List expressions are also predicates — an empty list 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 variables 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. 

MATCH (tobias { name: 'Tobias' }),(others)
WHERE others.name IN ['Andres', 'Peter'] AND (tobias)<--(others)
RETURN others

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

Result. 

+--------------------------------------------+
| others                                     |
+--------------------------------------------+
| Node[0]{name:"Andres",age:36,belt:"white"} |
+--------------------------------------------+
1 row

Filter on patterns using NOT

The NOT function can be used to exclude a pattern.

Query. 

MATCH (persons),(peter { name: 'Peter' })
WHERE NOT (persons)-->(peter)
RETURN persons

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

Result. 

+----------------------------------------------------------+
| persons                                                  |
+----------------------------------------------------------+
| Node[1]{address:"Sweden/Malmo",name:"Tobias",age:25}     |
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
2 rows

Filter on patterns with properties

You can also add properties to your patterns:

Query. 

MATCH (n)
WHERE (n)-[:KNOWS]-({ name:'Tobias' })
RETURN n

Finds all nodes that have a KNOWS relationship to a node with the name "Tobias".

Result. 

+--------------------------------------------+
| n                                          |
+--------------------------------------------+
| Node[0]{name:"Andres",age:36,belt:"white"} |
+--------------------------------------------+
1 row

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. 

MATCH (n)-[r]->()
WHERE n.name='Andres' AND type(r)=~ 'K.*'
RETURN r

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

Result. 

+-----------------------+
| r                     |
+-----------------------+
| :KNOWS[1]{since:1999} |
| :KNOWS[0]{since:2012} |
+-----------------------+
2 rows

3.3.3.5. Lists

IN operator

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

Query. 

MATCH (a)
WHERE a.name IN ["Peter", "Tobias"]
RETURN a

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

Result. 

+----------------------------------------------------------+
| a                                                        |
+----------------------------------------------------------+
| Node[1]{address:"Sweden/Malmo",name:"Tobias",age:25}     |
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
2 rows

3.3.3.6. Missing properties and values

Default to false if property is missing

As missing properties evaluate to NULL, the comparision in the example will evaluate to FALSE for nodes without the belt property.

Query. 

MATCH (n)
WHERE n.belt = 'white'
RETURN n

Only nodes with white belts are returned.

Result. 

+--------------------------------------------+
| n                                          |
+--------------------------------------------+
| Node[0]{name:"Andres",age:36,belt:"white"} |
+--------------------------------------------+
1 row

Default to true if property is missing

If you want to compare a property on a graph element, but only if it exists, you can compare the property against both the value you are looking for and NULL, like:

Query. 

MATCH (n)
WHERE n.belt = 'white' OR n.belt IS NULL RETURN n
ORDER BY n.name

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

Result. 

+----------------------------------------------------------+
| n                                                        |
+----------------------------------------------------------+
| Node[0]{name:"Andres",age:36,belt:"white"}               |
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
| Node[1]{address:"Sweden/Malmo",name:"Tobias",age:25}     |
+----------------------------------------------------------+
3 rows

Filter on NULL

Sometimes you might want to test if a value or a variable 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. 

MATCH (person)
WHERE person.name = 'Peter' AND person.belt IS NULL RETURN person

Nodes that have name Peter but no belt property are returned.

Result. 

+----------------------------------------------------------+
| person                                                   |
+----------------------------------------------------------+
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
1 row

3.3.3.7. Using ranges

Simple range

To check for an element being inside a specific range, use the inequality operators <, , >=, >.

Query. 

MATCH (a)
WHERE a.name >= 'Peter'
RETURN a

Nodes having a name property lexicographically greater than or equal to 'Peter' are returned.

Result. 

+----------------------------------------------------------+
| a                                                        |
+----------------------------------------------------------+
| Node[1]{address:"Sweden/Malmo",name:"Tobias",age:25}     |
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
2 rows

Composite range

Several inequalities can be used to construct a range.

Query. 

MATCH (a)
WHERE a.name > 'Andres' AND a.name < 'Tobias'
RETURN a

Nodes having a name property lexicographically between 'Andres' and 'Tobias' are returned.

Result. 

+----------------------------------------------------------+
| a                                                        |
+----------------------------------------------------------+
| Node[2]{email:"peter_n@example.com",name:"Peter",age:34} |
+----------------------------------------------------------+
1 row

3.3.4. START

Find starting points through legacy indexes.

The START clause should only be used when accessing legacy indexes. In all other cases, use MATCH instead (see Section 3.3.1, “MATCH”).

In Cypher, every query describes a pattern, and in that pattern one can have multiple starting points. A starting point is a relationship or a node where a pattern is anchored. Using START you can only introduce starting points by legacy index seeks. Note that trying to use a legacy index that doesn’t exist will generate an error.

This is the graph the examples are using:

Figure 3.7. Graph
alt

3.3.4.1. Get node or relationship from index

Node by index seek

When the starting point can be found by using index seeks, it can be done like this: node:index-name(key = "value"). In this example, there exists a node index named nodes.

Query. 

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

The query returns the node indexed with the name "A".

Result. 

+-------------------+
| n                 |
+-------------------+
| Node[0]{name:"A"} |
+-------------------+
1 row

Relationship by index seek

When the starting point can be found by using index seeks, 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                        |
+--------------------------+
| :KNOWS[0]{name:"Andrés"} |
+--------------------------+
1 row

Node by index query

When the starting point can be found by more complex Lucene queries, this is the syntax to use: node:index-name("query").This allows you to write more advanced index queries.

Query. 

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

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

Result. 

+-------------------+
| n                 |
+-------------------+
| Node[0]{name:"A"} |
+-------------------+
1 row

3.3.5. Aggregation

3.3.5.1. 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 calculates 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 subgraphs, 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. In this case, we are running the query against the following data:

alt

Query. 

MATCH (me:Person)-->(friend:Person)-->(friend_of_friend:Person)
WHERE me.name = 'A'
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. In this case, both B and C know D and thus D will get counted twice, when not using DISTINCT.

Result. 

+------------------------------------------------------------+
| count(distinct friend_of_friend) | count(friend_of_friend) |
+------------------------------------------------------------+
| 1                                | 2                       |
+------------------------------------------------------------+
1 row

The following examples are assuming the example graph structure below.

Figure 3.8. Graph
alt

3.3.5.2. COUNT

COUNT is used to count the number of rows.

COUNT can be used in two forms — COUNT(*) which just counts the number of matching rows, and COUNT(<expression>), which counts the number of non-NULL values in <expression>.

Count nodes

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

Query. 

MATCH (n { name: 'A' })-->(x)
RETURN n, count(*)

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

Result. 

+------------------------------------------+
| n                             | count(*) |
+------------------------------------------+
| Node[0]{name:"A",property:13} | 3        |
+------------------------------------------+
1 row

Group Count Relationship Types

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

Query. 

MATCH (n { name: 'A' })-[r]->()
RETURN type(r), count(*)

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

Result. 

+--------------------+
| type(r) | count(*) |
+--------------------+
| "KNOWS" | 3        |
+--------------------+
1 row

Count entities

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

Query. 

MATCH (n { name: 'A' })-->(x)
RETURN count(x)

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

Result. 

+----------+
| count(x) |
+----------+
| 3        |
+----------+
1 row

Count non-null values

You can count the non-NULL values by using count(<expression>).

Query. 

MATCH (n:Person)
RETURN count(n.property)

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

Result. 

+-------------------+
| count(n.property) |
+-------------------+
| 3                 |
+-------------------+
1 row

3.3.5.3. Statistics

sum

The sum aggregation function simply sums all the numeric values it encounters. NULLs are silently dropped.

Query. 

MATCH (n:Person)
RETURN sum(n.property)

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

Result. 

+-----------------+
| sum(n.property) |
+-----------------+
| 90              |
+-----------------+
1 row

avg

avg calculates the average of a numeric column.

Query. 

MATCH (n:Person)
RETURN avg(n.property)

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

Result. 

+-----------------+
| avg(n.property) |
+-----------------+
| 30.0            |
+-----------------+
1 row

percentileDisc

percentileDisc 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 percentileCont.

Query. 

MATCH (n:Person)
RETURN percentileDisc(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. 

+---------------------------------+
| percentileDisc(n.property, 0.5) |
+---------------------------------+
| 33                              |
+---------------------------------+
1 row

percentileCont

percentileCont 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 percentileDisc.

Query. 

MATCH (n:Person)
RETURN percentileCont(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. 

+---------------------------------+
| percentileCont(n.property, 0.4) |
+---------------------------------+
| 29.0                            |
+---------------------------------+
1 row

stdev

stdev calculates the standard deviation for a given value over a group. It uses a standard two-pass method, with N - 1 as the denominator, and should be used when taking a sample of the population for an unbiased estimate. When the standard variation of the entire population is being calculated, stdevp should be used.

Query. 

MATCH (n)
WHERE n.name IN ['A', 'B', 'C']
RETURN stdev(n.property)

The standard deviation of the values in the property property is returned by the example query.

Result. 

+--------------------+
| stdev(n.property)  |
+--------------------+
| 15.716233645501712 |
+--------------------+
1 row

stdevp

stdevp calculates the standard deviation for a given value over a group. It uses a standard two-pass method, with N as the denominator, and should be used when calculating the standard deviation for an entire population. When the standard variation of only a sample of the population is being calculated, stdev should be used.

Query. 

MATCH (n)
WHERE n.name IN ['A', 'B', 'C']
RETURN stdevp(n.property)

The population standard deviation of the values in the property property is returned by the example query.

Result. 

+--------------------+
| stdevp(n.property) |
+--------------------+
| 12.832251036613439 |
+--------------------+
1 row

max

max find the largest value in a numeric column.

Query. 

MATCH (n:Person)
RETURN max(n.property)

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

Result. 

+-----------------+
| max(n.property) |
+-----------------+
| 44              |
+-----------------+
1 row

min

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

Query. 

MATCH (n:Person)
RETURN min(n.property)

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

Result. 

+-----------------+
| min(n.property) |
+-----------------+
| 13              |
+-----------------+
1 row

3.3.5.4. collect

collect collects all the values into a list. It will ignore NULLs.

Query. 

MATCH (n:Person)
RETURN collect(n.property)

Returns a single row, with all the values collected.

Result. 

+---------------------+
| collect(n.property) |
+---------------------+
| [13,33,44]          |
+---------------------+
1 row

3.3.5.5. 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. 

MATCH (a:Person { name: 'A' })-->(b)
RETURN count(DISTINCT b.eyes)

Returns the number of eye colors.

Result. 

+------------------------+
| count(distinct b.eyes) |
+------------------------+
| 2                      |
+------------------------+
1 row

3.3.6. LOAD CSV

LOAD CSV is used to import data from CSV files.

  • The URL of the CSV file is specified by using FROM followed by an arbitrary expression evaluating to the URL in question.
  • It is required to specify a variable for the CSV data using AS.
  • LOAD CSV supports resources compressed with gzip, Deflate, as well as ZIP archives.
  • CSV files can be stored on the database server and are then accessible using a file:/// URL. Alternatively, LOAD CSV also supports accessing CSV files via HTTPS, HTTP, and FTP.
  • LOAD CSV will follow HTTP redirects but for security reasons it will not follow redirects that changes the protocol, for example if the redirect is going from HTTPS to HTTP.
dbms.security.allow_csv_import_from_file_urls
This setting determines if Cypher will allow the use of file:/// URLs when loading data using LOAD CSV. Such URLs identify files on the filesystem of the database server. Default is true.
dbms.directories.import
Sets the root directory for file:/// URLs used with the Cypher LOAD CSV clause. This must be set to a single directory on the filesystem of the database server, and will make all requests to load from file:/// URLs relative to the specified directory (similar to how a unix chroot operates). The default value is import. This is a security measure which prevents the database from accessing files outside the standard import directory.

File URLs will be resolved relative to the dbms.directories.import directory. For example, a file URL will typically look like file:///myfile.csv or file:///myproject/myfile.csv.

  • If dbms.directories.import is set to the default value import, using the above URLs in LOAD CSV would read from <NEO4J_HOME>/import/myfile.csv and <NEO4J_HOME>import/myproject/myfile.csv respectively.
  • If it is set to /data/csv, using the above URLs in LOAD CSV would read from /data/csv/myfile.csv and /data/csv/myproject/myfile.csv respectively.

See the examples below for further details.

There is also a worked example, see Section 3.3.14, “Importing CSV files with Cypher”.

3.3.6.1. CSV file format

The CSV file to use with LOAD CSV must have the following characteristics:

  • the character encoding is UTF-8;
  • the end line termination is system dependent, e.g., it is \n on unix or \r\n on windows;
  • the default field terminator is ,;
  • the field terminator character can be change by using the option FIELDTERMINATOR available in the LOAD CSV command;
  • quoted strings are allowed in the CSV file and the quotes are dropped when reading the data;
  • the character for string quotation is double quote ";
  • the escape character is \.

3.3.6.2. Import data from a CSV file

To import data from a CSV file into Neo4j, you can use LOAD CSV to get the data into your query. Then you write it to your database using the normal updating clauses of Cypher.

artists.csv. 

"1","ABBA","1992"
"2","Roxette","1986"
"3","Europe","1979"
"4","The Cardigans","1992"

Query. 

LOAD CSV FROM 'http://neo4j.com/docs/3.0.7/csv/artists.csv' AS line
CREATE (:Artist { name: line[1], year: toInt(line[2])})

A new node with the Artist label is created for each row in the CSV file. In addition, two columns from the CSV file are set as properties on the nodes.

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 4
Properties set: 8
Labels added: 4

3.3.6.3. Import data from a CSV file containing headers

When your CSV file has headers, you can view each row in the file as a map instead of as an array of strings.

artists-with-headers.csv. 

"Id","Name","Year"
"1","ABBA","1992"
"2","Roxette","1986"
"3","Europe","1979"
"4","The Cardigans","1992"

Query. 

LOAD CSV WITH HEADERS FROM 'http://neo4j.com/docs/3.0.7/csv/artists-with-headers.csv' AS line
CREATE (:Artist { name: line.Name, year: toInt(line.Year)})

This time, the file starts with a single row containing column names. Indicate this using WITH HEADERS and you can access specific fields by their corresponding column name.

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 4
Properties set: 8
Labels added: 4

3.3.6.4. Import data from a CSV file with a custom field delimiter

Sometimes, your CSV file has other field delimiters than commas. You can specify which delimiter your file uses using FIELDTERMINATOR.

artists-fieldterminator.csv. 

"1";"ABBA";"1992"
"2";"Roxette";"1986"
"3";"Europe";"1979"
"4";"The Cardigans";"1992"

Query. 

LOAD CSV FROM 'http://neo4j.com/docs/3.0.7/csv/artists-fieldterminator.csv' AS line FIELDTERMINATOR
  ';'
CREATE (:Artist { name: line[1], year: toInt(line[2])})

As values in this file are separated by a semicolon, a custom FIELDTERMINATOR is specified in the LOAD CSV clause.

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 4
Properties set: 8
Labels added: 4

3.3.6.5. Importing large amounts of data

If the CSV file contains a significant number of rows (approaching hundreds of thousands or millions), USING PERIODIC COMMIT can be used to instruct Neo4j to perform a commit after a number of rows. This reduces the memory overhead of the transaction state. By default, the commit will happen every 1000 rows. For more information, see Section 3.3.15, “USING PERIODIC COMMIT”.

Query. 

USING PERIODIC COMMIT
LOAD CSV FROM 'http://neo4j.com/docs/3.0.7/csv/artists.csv' AS line
CREATE (:Artist { name: line[1], year: toInt(line[2])})

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 4
Properties set: 8
Labels added: 4

3.3.6.6. Setting the rate of periodic commits

You can set the number of rows as in the example, where it is set to 500 rows.

Query. 

USING PERIODIC COMMIT 500
LOAD CSV FROM 'http://neo4j.com/docs/3.0.7/csv/artists.csv' AS line
CREATE (:Artist { name: line[1], year: toInt(line[2])})

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 4
Properties set: 8
Labels added: 4

3.3.6.7. Import data containing escaped characters

In this example, we both have additional quotes around the values, as well as escaped quotes inside one value.

artists-with-escaped-char.csv. 

"1","The ""Symbol""","1992"

Query. 

LOAD CSV FROM 'http://neo4j.com/docs/3.0.7/csv/artists-with-escaped-char.csv' AS line
CREATE (a:Artist { name: line[1], year: toInt(line[2])})
RETURN a.name AS name, a.year AS year, length(a.name) AS length

Note that strings are wrapped in quotes in the output here. You can see that when comparing to the length of the string in this case!

Result. 

+--------------------------------+
| name           | year | length |
+--------------------------------+
| "The "Symbol"" | 1992 | 12     |
+--------------------------------+
1 row
Nodes created: 1
Properties set: 2
Labels added: 1

3.3.7. CREATE

The CREATE clause is used to create graph elements — nodes and relationships.

In the CREATE clause, patterns are used a lot. Read Section 3.2.7, “Patterns” for an introduction.

3.3.7.1. Create nodes

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. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 1

Create multiple nodes

Creating multiple nodes is done by separating them with a comma.

Query. 

CREATE (n),(m)

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 2

Create a node with a label

To add a label when creating a node, use the syntax below.

Query. 

CREATE (n:Person)

Nothing is returned from this query.

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 1
Labels added: 1

Create a node with multiple labels

To add labels when creating a node, use the syntax below. In this case, we add two labels.

Query. 

CREATE (n:Person:Swedish)

Nothing is returned from this query.

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 1
Labels added: 2

Create node and add labels and properties

When creating a new node with labels, you can add properties at the same time.

Query. 

CREATE (n:Person { name : 'Andres', title : 'Developer' })

Nothing is returned from this query.

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 1
Properties set: 2
Labels added: 1

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.

Result. 

+------------------------+
| a                      |
+------------------------+
| Node[0]{name:"Andres"} |
+------------------------+
1 row
Nodes created: 1
Properties set: 1

3.3.7.2. Create relationships

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. 

MATCH (a:Person),(b:Person)
WHERE a.name = 'Node A' AND b.name = 'Node B'
CREATE (a)-[r:RELTYPE]->(b)
RETURN r

The created relationship is returned by the query.

Result. 

+---------------+
| r             |
+---------------+
| :RELTYPE[0]{} |
+---------------+
1 row
Relationships created: 1

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. 

MATCH (a:Person),(b:Person)
WHERE a.name = 'Node A' AND b.name = 'Node B'
CREATE (a)-[r:RELTYPE { name : a.name + '<->' + b.name }]->(b)
RETURN r

The newly created relationship is returned by the example query.

Result. 

+-------------------------------------+
| r                                   |
+-------------------------------------+
| :RELTYPE[0]{name:"Node A<->Node B"} |
+-------------------------------------+
1 row
Relationships created: 1
Properties set: 1

3.3.7.3. 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 variable, and returns it.

Result. 

+------------------------------------------------------------------------------------------+
| p                                                                                        |
+------------------------------------------------------------------------------------------+
| [Node[0]{name:"Andres"},:WORKS_AT[0]{},Node[1]{},:WORKS_AT[1]{},Node[2]{name:"Michael"}] |
+------------------------------------------------------------------------------------------+
1 row
Nodes created: 3
Relationships created: 2
Properties set: 2

3.3.7.4. Use parameters with CREATE

Create node with a parameter for the properties

You can also create a graph entity from a map. All the key/value pairs in the map will be set as properties on the created relationship or node. In this case we add a Person label to the node as well.

Parameters. 

{
  "props" : {
    "name" : "Andres",
    "position" : "Developer"
  }
}

Query. 

CREATE (n:Person { props })
RETURN n

Result. 

+---------------------------------------------+
| n                                           |
+---------------------------------------------+
| Node[0]{name:"Andres",position:"Developer"} |
+---------------------------------------------+
1 row
Nodes created: 1
Properties set: 2
Labels added: 1

Create multiple nodes with a parameter for their properties

By providing Cypher an array of maps, it will create a node for each map.

Parameters. 

{
  "props" : [ {
    "name" : "Andres",
    "position" : "Developer"
  }, {
    "name" : "Michael",
    "position" : "Developer"
  } ]
}

Query. 

UNWIND { props } AS map
CREATE (n)
SET n = map

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 2
Properties set: 4

3.3.8. MERGE

The MERGE clause ensures that a pattern exists in the graph. Either the pattern already exists, or it needs to be created.

3.3.8.1. Introduction

MERGE either matches existing nodes and binds them, or it creates new data and binds that. It’s like a combination of MATCH and CREATE that additionally allows you to specify what happens if the data was matched or created.

For example, you can specify that the graph must contain a node for a user with a certain name. If there isn’t a node with the correct name, a new node will be created and its name property set.

When using MERGE on full patterns, the behavior is that either the whole pattern matches, or the whole pattern is created. MERGE will not partially use existing patterns — it’s all or nothing. If partial matches are needed, this can be accomplished by splitting a pattern up into multiple MERGE clauses.

As with MATCH, MERGE can match multiple occurrences of a pattern. If there are multiple matches, they will all be passed on to later stages of the query.

The last part of MERGE is the ON CREATE and ON MATCH. These allow a query to express additional changes to the properties of a node or relationship, depending on if the element was MATCHed in the database or if it was CREATEd.

The rule planner (see Section 3.6.1, “How are queries executed?”) expands a MERGE pattern from the end point that has the variable with the lowest lexicographical order. This means that it might choose a suboptimal expansion path, expanding from a node with a higher degree. The pattern MERGE (a:A)-[:R]→(b:B) will always expand from a to b, so if it is known that b nodes are a better choice for start point, renaming variables could improve performance.

The following graph is used for the examples below:

Figure 3.9. Graph
alt

3.3.8.2. Merge nodes

Merge single node with a label

Merging a single node with a given label.

Query. 

MERGE (robert:Critic)
RETURN robert, labels(robert)

A new node is created because there are no nodes labeled Critic in the database.

Result. 

+----------------------------+
| robert    | labels(robert) |
+----------------------------+
| Node[7]{} | ["Critic"]     |
+----------------------------+
1 row
Nodes created: 1
Labels added: 1

Merge single node with properties

Merging a single node with properties where not all properties match any existing node.

Query. 

MERGE (charlie { name:'Charlie Sheen', age:10 })
RETURN charlie

A new node with the name 'Charlie Sheen' will be created since not all properties matched the existing 'Charlie Sheen' node.

Result. 

+--------------------------------------+
| charlie                              |
+--------------------------------------+
| Node[7]{name:"Charlie Sheen",age:10} |
+--------------------------------------+
1 row
Nodes created: 1
Properties set: 2

Merge single node specifying both label and property

Merging a single node with both label and property matching an existing node.

Query. 

MERGE (michael:Person { name:'Michael Douglas' })
RETURN michael.name, michael.bornIn

'Michael Douglas' will be matched and the name and bornIn properties returned.

Result. 

+------------------------------------+
| michael.name      | michael.bornIn |
+------------------------------------+
| "Michael Douglas" | "New Jersey"   |
+------------------------------------+
1 row

Merge single node derived from an existing node property

For some property 'p' in each bound node in a set of nodes, a single new node is created for each unique value for 'p'.

Query. 

MATCH (person:Person)
MERGE (city:City { name: person.bornIn })
RETURN person.name, person.bornIn, city

Three nodes labeled City are created, each of which contains a 'name' property with the value of 'New York', 'Ohio', and 'New Jersey', respectively. Note that even though the MATCH clause results in three bound nodes having the value 'New York' for the 'bornIn' property, only a single 'New York' node (i.e. a City node with a name of 'New York') is created. As the 'New York' node is not matched for the first bound node, it is created. However, the newly-created 'New York' node is matched and bound for the second and third bound nodes.

Result. 

+----------------------------------------------------------------+
| person.name       | person.bornIn | city                       |
+----------------------------------------------------------------+
| "Rob Reiner"      | "New York"    | Node[7]{name:"New York"}   |
| "Oliver Stone"    | "New York"    | Node[7]{name:"New York"}   |
| "Charlie Sheen"   | "New York"    | Node[7]{name:"New York"}   |
| "Michael Douglas" | "New Jersey"  | Node[8]{name:"New Jersey"} |
| "Martin Sheen"    | "Ohio"        | Node[9]{name:"Ohio"}       |
+----------------------------------------------------------------+
5 rows
Nodes created: 3
Properties set: 3
Labels added: 3

3.3.8.3. Use ON CREATE and ON MATCH

Merge with ON CREATE

Merge a node and set properties if the node needs to be created.

Query. 

MERGE (keanu:Person { name:'Keanu Reeves' })
ON CREATE SET keanu.created = timestamp()
RETURN keanu.name, keanu.created

The query creates the 'keanu' node and sets a timestamp on creation time.

Result. 

+--------------------------------+
| keanu.name     | keanu.created |
+--------------------------------+
| "Keanu Reeves" | 1480347889742 |
+--------------------------------+
1 row
Nodes created: 1
Properties set: 2
Labels added: 1

Merge with ON MATCH

Merging nodes and setting properties on found nodes.

Query. 

MERGE (person:Person)
ON MATCH SET person.found = TRUE RETURN person.name, person.found

The query finds all the Person nodes, sets a property on them, and returns them.

Result. 

+----------------------------------+
| person.name       | person.found |
+----------------------------------+
| "Rob Reiner"      | true         |
| "Oliver Stone"    | true         |
| "Charlie Sheen"   | true         |
| "Michael Douglas" | true         |
| "Martin Sheen"    | true         |
+----------------------------------+
5 rows
Properties set: 5

Merge with ON CREATE and ON MATCH

Merge a node and set properties if the node needs to be created.

Query. 

MERGE (keanu:Person { name:'Keanu Reeves' })
ON CREATE SET keanu.created = timestamp()
ON MATCH SET keanu.lastSeen = timestamp()
RETURN keanu.name, keanu.created, keanu.lastSeen

The query creates the 'keanu' node, and sets a timestamp on creation time. If 'keanu' had already existed, a different property would have been set.

Result. 

+-------------------------------------------------+
| keanu.name     | keanu.created | keanu.lastSeen |
+-------------------------------------------------+
| "Keanu Reeves" | 1480347893066 | <null>         |
+-------------------------------------------------+
1 row
Nodes created: 1
Properties set: 2
Labels added: 1

Merge with ON MATCH setting multiple properties

If multiple properties should be set, simply separate them with commas.

Query. 

MERGE (person:Person)
ON MATCH SET person.found = TRUE , person.lastAccessed = timestamp()
RETURN person.name, person.found, person.lastAccessed

Result. 

+--------------------------------------------------------+
| person.name       | person.found | person.lastAccessed |
+--------------------------------------------------------+
| "Rob Reiner"      | true         | 1480347891806       |
| "Oliver Stone"    | true         | 1480347891806       |
| "Charlie Sheen"   | true         | 1480347891806       |
| "Michael Douglas" | true         | 1480347891806       |
| "Martin Sheen"    | true         | 1480347891806       |
+--------------------------------------------------------+
5 rows
Properties set: 10

3.3.8.4. Merge relationships

Merge on a relationship

MERGE can be used to match or create a relationship.

Query. 

MATCH (charlie:Person { name:'Charlie Sheen' }),(wallStreet:Movie { title:'Wall Street' })
MERGE (charlie)-[r:ACTED_IN]->(wallStreet)
RETURN charlie.name, type(r), wallStreet.title

'Charlie Sheen' had already been marked as acting in 'Wall Street', so the existing relationship is found and returned. Note that in order to match or create a relationship when using MERGE, at least one bound node must be specified, which is done via the MATCH clause in the above example.

Result. 

+-------------------------------------------------+
| charlie.name    | type(r)    | wallStreet.title |
+-------------------------------------------------+
| "Charlie Sheen" | "ACTED_IN" | "Wall Street"    |
+-------------------------------------------------+
1 row

Merge on multiple relationships

When MERGE is used on a whole pattern, either everything matches, or everything is created.

Query. 

MATCH (oliver:Person { name:'Oliver Stone' }),(reiner:Person { name:'Rob Reiner' })
MERGE (oliver)-[:DIRECTED]->(movie:Movie)<-[:ACTED_IN]-(reiner)
RETURN movie

In our example graph, 'Oliver Stone' and 'Rob Reiner' have never worked together. When we try to MERGE a movie between them, Neo4j will not use any of the existing movies already connected to either person. Instead, a new 'movie' node is created.

Result. 

+-----------+
| movie     |
+-----------+
| Node[7]{} |
+-----------+
1 row
Nodes created: 1
Relationships created: 2
Labels added: 1

Merge on an undirected relationship

MERGE can also be used with an undirected relationship. When it needs to create a new one, it will pick a direction.

Query. 

MATCH (charlie:Person { name:'Charlie Sheen' }),(oliver:Person { name:'Oliver Stone' })
MERGE (charlie)-[r:KNOWS]-(oliver)
RETURN r

As 'Charlie Sheen' and 'Oliver Stone' do not know each other, this MERGE query will create a :KNOWS relationship between them. The direction of the created relationship is arbitrary.

Result. 

+-------------+
| r           |
+-------------+
| :KNOWS[8]{} |
+-------------+
1 row
Relationships created: 1

Merge on a relationship between two existing nodes

MERGE can be used in conjunction with preceding MATCH and MERGE clauses to create a relationship between two bound nodes 'm' and 'n', where 'm' is returned by MATCH and 'n' is created or matched by the earlier MERGE.

Query. 

MATCH (person:Person)
MERGE (city:City { name: person.bornIn })
MERGE (person)-[r:BORN_IN]->(city)
RETURN person.name, person.bornIn, city

This builds on the example from the section called “Merge single node derived from an existing node property”. The second MERGE creates a BORN_IN relationship between each person and a city corresponding to the value of the person’s 'bornIn' property. 'Charlie Sheen', 'Rob Reiner' and 'Oliver Stone' all have a BORN_IN relationship to the 'same' City node ('New York').

Result. 

+----------------------------------------------------------------+
| person.name       | person.bornIn | city                       |
+----------------------------------------------------------------+
| "Rob Reiner"      | "New York"    | Node[7]{name:"New York"}   |
| "Oliver Stone"    | "New York"    | Node[7]{name:"New York"}   |
| "Charlie Sheen"   | "New York"    | Node[7]{name:"New York"}   |
| "Michael Douglas" | "New Jersey"  | Node[8]{name:"New Jersey"} |
| "Martin Sheen"    | "Ohio"        | Node[9]{name:"Ohio"}       |
+----------------------------------------------------------------+
5 rows
Nodes created: 3
Relationships created: 5
Properties set: 3
Labels added: 3

Merge on a relationship between an existing node and a merged node derived from a node property

MERGE can be used to simultaneously create both a new node 'n' and a relationship between a bound node 'm' and 'n'.

Query. 

MATCH (person:Person)
MERGE (person)-[r:HAS_CHAUFFEUR]->(chauffeur:Chauffeur { name: person.chauffeurName })
RETURN person.name, person.chauffeurName, chauffeur

As MERGE found no matches — in our example graph, there are no nodes labeled with Chauffeur and no HAS_CHAUFFEUR relationships — MERGE creates five nodes labeled with Chauffeur, each of which contains a 'name' property whose value corresponds to each matched Person node’s 'chauffeurName' property value. MERGE also creates a HAS_CHAUFFEUR relationship between each Person node and the newly-created corresponding Chauffeur node. As 'Charlie Sheen' and 'Michael Douglas' both have a chauffeur with the same name — 'John Brown' — a new node is created in each case, resulting in 'two' Chauffeur nodes having a 'name' of 'John Brown', correctly denoting the fact that even though the 'name' property may be identical, these are two separate people. This is in contrast to the example shown above in the section called “Merge on a relationship between two existing nodes”, where we used the first MERGE to bind the City nodes to prevent them from being recreated (and thus duplicated) in the second MERGE.

Result. 

+------------------------------------------------------------------------+
| person.name       | person.chauffeurName | chauffeur                   |
+------------------------------------------------------------------------+
| "Rob Reiner"      | "Ted Green"          | Node[7]{name:"Ted Green"}   |
| "Oliver Stone"    | "Bill White"         | Node[8]{name:"Bill White"}  |
| "Charlie Sheen"   | "John Brown"         | Node[9]{name:"John Brown"}  |
| "Michael Douglas" | "John Brown"         | Node[10]{name:"John Brown"} |
| "Martin Sheen"    | "Bob Brown"          | Node[11]{name:"Bob Brown"}  |
+------------------------------------------------------------------------+
5 rows
Nodes created: 5
Relationships created: 5
Properties set: 5
Labels added: 5

3.3.8.5. Using unique constraints with MERGE

Cypher prevents getting conflicting results from MERGE when using patterns that involve uniqueness constrains. In this case, there must be at most one node that matches that pattern.

For example, given two uniqueness constraints on :Person(id) and :Person(ssn): then a query such as MERGE (n:Person {id: 12, ssn: 437}) will fail, if there are two different nodes (one with id 12 and one with ssn 437) or if there is only one node with only one of the properties. In other words, there must be exactly one node that matches the pattern, or no matching nodes.

Note that the following examples assume the existence of uniqueness constraints that have been created using:

CREATE CONSTRAINT ON (n:Person) ASSERT n.name IS UNIQUE;
CREATE CONSTRAINT ON (n:Person) ASSERT n.role IS UNIQUE;
Merge using unique constraints creates a new node if no node is found

Merge using unique constraints creates a new node if no node is found.

Query. 

MERGE (laurence:Person { name: 'Laurence Fishburne' })
RETURN laurence.name

The query creates the 'laurence' node. If 'laurence' had already existed, MERGE would just match the existing node.

Result. 

+----------------------+
| laurence.name        |
+----------------------+
| "Laurence Fishburne" |
+----------------------+
1 row
Nodes created: 1
Properties set: 1
Labels added: 1

Merge using unique constraints matches an existing node

Merge using unique constraints matches an existing node.

Query. 

MERGE (oliver:Person { name:'Oliver Stone' })
RETURN oliver.name, oliver.bornIn

The 'oliver' node already exists, so MERGE just matches it.

Result. 

+--------------------------------+
| oliver.name    | oliver.bornIn |
+--------------------------------+
| "Oliver Stone" | "New York"    |
+--------------------------------+
1 row

Merge with unique constraints and partial matches

Merge using unique constraints fails when finding partial matches.

Query. 

MERGE (michael:Person { name:'Michael Douglas', role:'Gordon Gekko' })
RETURN michael

While there is a matching unique 'michael' node with the name 'Michael Douglas', there is no unique node with the role of 'Gordon Gekko' and MERGE fails to match.

Error message. 

Merge did not find a matching node and can not create a new node due to conflicts
with both existing and missing unique nodes. The conflicting constraints are on:
:Person.name and :Person.role

Merge with unique constraints and conflicting matches

Merge using unique constraints fails when finding conflicting matches.

Query. 

MERGE (oliver:Person { name:'Oliver Stone', role:'Gordon Gekko' })
RETURN oliver

While there is a matching unique 'oliver' node with the name 'Oliver Stone', there is also another unique node with the role of 'Gordon Gekko' and MERGE fails to match.

Error message. 

Merge did not find a matching node and can not create a new node due to conflicts
with both existing and missing unique nodes. The conflicting constraints are on:
:Person.name and :Person.role

3.3.8.6. Using map parameters with MERGE

MERGE does not support map parameters like for example CREATE does. To use map parameters with MERGE, it is necessary to explicitly use the expected properties, like in the following example. For more information on parameters, see Section 3.2.4, “Parameters”.

Parameters. 

{
  "param" : {
    "name" : "Keanu Reeves",
    "role" : "Neo"
  }
}

Query. 

MERGE (person:Person { name: { param }.name, role: { param }.role })
RETURN person.name, person.role

Result. 

+------------------------------+
| person.name    | person.role |
+------------------------------+
| "Keanu Reeves" | "Neo"       |
+------------------------------+
1 row
Nodes created: 1
Properties set: 2
Labels added: 1

3.3.9. SET

The SET clause is used to update labels on nodes and properties on nodes and relationships.

SET can also be used with maps from parameters to set properties.

Setting labels on a node is an idempotent operations — if you try to set a label on a node that already has that label on it, nothing happens. The query statistics will tell you if something needed to be done or not.

The examples use this graph as a starting point:

alt

3.3.9.1. Set a property

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

Query. 

MATCH (n { name: 'Andres' })
SET n.surname = 'Taylor'
RETURN n

The newly changed node is returned by the query.

Result. 

+------------------------------------------------------------+
| n                                                          |
+------------------------------------------------------------+
| Node[0]{surname:"Taylor",name:"Andres",age:36,hungry:true} |
+------------------------------------------------------------+
1 row
Properties set: 1

3.3.9.2. Remove a property

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

Query. 

MATCH (n { name: 'Andres' })
SET n.name = NULL RETURN n

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

Result. 

+-----------------------------+
| n                           |
+-----------------------------+
| Node[0]{hungry:true,age:36} |
+-----------------------------+
1 row
Properties set: 1

3.3.9.3. 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. 

MATCH (at { name: 'Andres' }),(pn { name: 'Peter' })
SET at = pn
RETURN at, pn

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

Result. 

+-------------------------------------------------------------+
| at                           | pn                           |
+-------------------------------------------------------------+
| Node[0]{name:"Peter",age:34} | Node[2]{name:"Peter",age:34} |
+-------------------------------------------------------------+
1 row
Properties set: 3

3.3.9.4. Adding properties from maps

When setting properties from a map (literal, paremeter, or graph element), you can use the += form of SET to only add properties, and not remove any of the existing properties on the graph element.

Query. 

MATCH (peter { name: 'Peter' })
SET peter += { hungry: TRUE , position: 'Entrepreneur' }

Result. 

+-------------------+
| No data returned. |
+-------------------+
Properties set: 2

3.3.9.5. Set a property using a parameter

Use a parameter to give the value of a property.

Parameters. 

{
  "surname" : "Taylor"
}

Query. 

MATCH (n { name: 'Andres' })
SET n.surname = { surname }
RETURN n

The Andres node has got an surname added.

Result. 

+------------------------------------------------------------+
| n                                                          |
+------------------------------------------------------------+
| Node[0]{surname:"Taylor",name:"Andres",age:36,hungry:true} |
+------------------------------------------------------------+
1 row
Properties set: 1

3.3.9.6. Set all properties using a parameter

This will replace all existing properties on the node with the new set provided by the parameter.

Parameters. 

{
  "props" : {
    "name" : "Andres",
    "position" : "Developer"
  }
}

Query. 

MATCH (n { name: 'Andres' })
SET n = { props }
RETURN n

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

Result. 

+---------------------------------------------+
| n                                           |
+---------------------------------------------+
| Node[0]{name:"Andres",position:"Developer"} |
+---------------------------------------------+
1 row
Properties set: 4

3.3.9.7. Set multiple properties using one SET clause

If you want to set multiple properties in one go, simply separate them with a comma.

Query. 

MATCH (n { name: 'Andres' })
SET n.position = 'Developer', n.surname = 'Taylor'

Result. 

+-------------------+
| No data returned. |
+-------------------+
Properties set: 2

3.3.9.8. Set a label on a node

To set a label on a node, use SET.

Query. 

MATCH (n { name: 'Stefan' })
SET n :German
RETURN n

The newly labeled node is returned by the query.

Result. 

+------------------------+
| n                      |
+------------------------+
| Node[3]{name:"Stefan"} |
+------------------------+
1 row
Labels added: 1

3.3.9.9. Set multiple labels on a node

To set multiple labels on a node, use SET and separate the different labels using :.

Query. 

MATCH (n { name: 'Emil' })
SET n :Swedish:Bossman
RETURN n

The newly labeled node is returned by the query.

Result. 

+----------------------+
| n                    |
+----------------------+
| Node[1]{name:"Emil"} |
+----------------------+
1 row
Labels added: 2

3.3.10. DELETE

The DELETE clause is used to delete graph elements — nodes, relationships or paths.

For removing properties and labels, see Section 3.3.11, “REMOVE”. Remember that you can not delete a node without also deleting relationships that start or end on said node. Either explicitly delete the relationships, or use DETACH DELETE.

The examples start out with the following database:

alt

3.3.10.1. Delete single node

To delete a node, use the DELETE clause.

Query. 

MATCH (n:Useless)
DELETE n

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes deleted: 1

3.3.10.2. Delete all nodes and relationships

This query isn’t for deleting large amounts of data, but is nice when playing around with small example data sets.

Query. 

MATCH (n)
DETACH DELETE n

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes deleted: 3
Relationships deleted: 2

3.3.10.3. Delete a node with all its relationships

When you want to delete a node and any relationship going to or from it, use DETACH DELETE.

Query. 

MATCH (n { name:'Andres' })
DETACH DELETE n

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes deleted: 1
Relationships deleted: 2

3.3.11. REMOVE

The REMOVE clause is used to remove properties and labels from graph elements.

For deleting nodes and relationships, see Section 3.3.10, “DELETE”.

Removing labels from a node is an idempotent operation: If you try to remove a label from a node that does not have that label on it, nothing happens. The query statistics will tell you if something needed to be done or not.

The examples start out with the following database:

alt

3.3.11.1. 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 REMOVE.

Query. 

MATCH (andres { name: 'Andres' })
REMOVE andres.age
RETURN andres

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

Result. 

+------------------------+
| andres                 |
+------------------------+
| Node[0]{name:"Andres"} |
+------------------------+
1 row
Properties set: 1

3.3.11.2. Remove a label from a node

To remove labels, you use REMOVE.

Query. 

MATCH (n { name: 'Peter' })
REMOVE n:German
RETURN n

Result. 

+------------------------------+
| n                            |
+------------------------------+
| Node[2]{name:"Peter",age:34} |
+------------------------------+
1 row
Labels removed: 1

3.3.11.3. Removing multiple labels

To remove multiple labels, you use REMOVE.

Query. 

MATCH (n { name: 'Peter' })
REMOVE n:German:Swedish
RETURN n

Result. 

+------------------------------+
| n                            |
+------------------------------+
| Node[2]{name:"Peter",age:34} |
+------------------------------+
1 row
Labels removed: 2

3.3.12. FOREACH

The FOREACH clause is used to update data within a list, whether components of a path, or result of aggregation.

Lists 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 path, or a list created by aggregation.

The variable context inside of the foreach parenthesis is separate from the one outside it. This means that if you CREATE a node variable 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 of the updating commands — CREATE, CREATE UNIQUE, MERGE, DELETE, and FOREACH.

If you want to execute an additional MATCH for each element in a list then UNWIND (see Section 3.3.21, “UNWIND”) would be a more appropriate command.

Figure 3.10. Data for the examples
alt

3.3.12.1. Mark all nodes along a path

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

Query. 

MATCH p =(begin)-[*]->(END )
WHERE begin.name='A' AND END .name='D'
FOREACH (n IN nodes(p)| SET n.marked = TRUE )

Nothing is returned from this query, but four properties are set.

Result. 

+-------------------+
| No data returned. |
+-------------------+
Properties set: 4

3.3.13. CREATE UNIQUE

The CREATE UNIQUE clause is a mix of MATCH and CREATE — it will match what it can, and create what is missing.

3.3.13.1. Introduction

MERGE might be what you want to use instead of CREATE UNIQUE. Note however, that MERGE doesn’t give as strong guarantees for relationships being 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 error will be generated.

In the CREATE UNIQUE clause, patterns are used a lot. Read Section 3.2.7, “Patterns” for an introduction.

The examples start out with the following data set:

alt

3.3.13.2. Create unique nodes

Create node if missing

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

Query. 

MATCH (root { name: 'root' })
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   |
+-----------+
| Node[4]{} |
+-----------+
1 row
Nodes created: 1
Relationships created: 1

Create nodes with values

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

Query. 

MATCH (root { name: 'root' })
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              |
+-------------------+
| Node[4]{name:"D"} |
+-------------------+
1 row
Nodes created: 1
Relationships created: 1
Properties set: 1

Create labeled node if missing

If the pattern described needs a labeled node and there is none with the given labels, Cypher will create a new one.

Query. 

MATCH (a { name: 'A' })
CREATE UNIQUE (a)-[:KNOWS]-(c:blue)
RETURN c

The A node is connected in a KNOWS relationship to the c node, but since C doesn’t have the :blue label, a new node labeled as :blue is created along with a KNOWS relationship from A to it.

Result. 

+-----------+
| c         |
+-----------+
| Node[4]{} |
+-----------+
1 row
Nodes created: 1
Relationships created: 1
Labels added: 1

3.3.13.3. Create unique relationships

Create relationship if it is missing

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

Query. 

MATCH (lft { name: 'A' }),(rgt)
WHERE rgt.name IN ['B', 'C']
CREATE UNIQUE (lft)-[r:KNOWS]->(rgt)
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           |
+-------------+
| :KNOWS[4]{} |
| :KNOWS[3]{} |
+-------------+
2 rows
Relationships created: 1

Create relationship with values

Relationships to be created can also be matched on values.

Query. 

MATCH (root { name: 'root' })
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                      |
+------------------------+
| :X[4]{since:"forever"} |
+------------------------+
1 row
Nodes created: 1
Relationships created: 1
Properties set: 1

3.3.13.4. Describe complex pattern

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

Query. 

MATCH (root { name: 'root' })
CREATE UNIQUE (root)-[:FOO]->(x),(root)-[:BAR]->(x)
RETURN x

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

Result. 

+-----------+
| x         |
+-----------+
| Node[4]{} |
+-----------+
1 row
Nodes created: 1
Relationships created: 2

3.3.14. Importing CSV files with Cypher

This tutorial will show you how to import data from CSV files using LOAD CSV.

In this example, we’re given three CSV files: a list of persons, a list of movies, and a list of which role was played by some of these persons in each movie.

CSV files can be stored on the database server and are then accessible using a file:// URL. Alternatively, LOAD CSV also supports accessing CSV files via HTTPS, HTTP, and FTP. LOAD CSV will follow HTTP redirects but for security reasons it will not follow redirects that changes the protocol, for example if the redirect is going from HTTPS to HTTP.

For more details, see Section 3.3.6, “LOAD CSV”.

Using the following Cypher queries, we’ll create a node for each person, a node for each movie and a relationship between the two with a property denoting the role. We’re also keeping track of the country in which each movie was made.

Let’s start with importing the persons:

LOAD CSV WITH HEADERS FROM "http://neo4j.com/docs/3.0.7/csv/import/persons.csv" AS csvLine
CREATE (p:Person { id: toInt(csvLine.id), name: csvLine.name })

The CSV file we’re using looks like this:

persons.csv. 

id,name
1,Charlie Sheen
2,Oliver Stone
3,Michael Douglas
4,Martin Sheen
5,Morgan Freeman

Now, let’s import the movies. This time, we’re also creating a relationship to the country in which the movie was made. If you are storing your data in a SQL database, this is the one-to-many relationship type.

We’re using MERGE to create nodes that represent countries. Using MERGE avoids creating duplicate country nodes in the case where multiple movies have been made in the same country.

When using MERGE or MATCH with LOAD CSV we need to make sure we have an index (see Section 3.5.1, “Indexes”) or a unique constraint (see Section 3.5.2, “Constraints”) on the property we’re merging. This will ensure the query executes in a performant way.

Before running our query to connect movies and countries we’ll create an index for the name property on the Country label to ensure the query runs as fast as it can:

CREATE INDEX ON :Country(name)
LOAD CSV WITH HEADERS FROM "http://neo4j.com/docs/3.0.7/csv/import/movies.csv" AS csvLine
MERGE (country:Country { name: csvLine.country })
CREATE (movie:Movie { id: toInt(csvLine.id), title: csvLine.title, year:toInt(csvLine.year)})
CREATE (movie)-[:MADE_IN]->(country)

movies.csv. 

id,title,country,year
1,Wall Street,USA,1987
2,The American President,USA,1995
3,The Shawshank Redemption,USA,1994

Lastly, we create the relationships between the persons and the movies. Since the relationship is a many to many relationship, one actor can participate in many movies, and one movie has many actors in it. We have this data in a separate file.

We’ll index the id property on Person and Movie nodes. The id property is a temporary property used to look up the appropriate nodes for a relationship when importing the third file. By indexing the id property, node lookup (e.g. by MATCH) will be much faster. Since we expect the ids to be unique in each set, we’ll create a unique constraint. This protects us from invalid data since constraint creation will fail if there are multiple nodes with the same id property. Creating a unique constraint also creates a unique index (which is faster than a regular index).

CREATE CONSTRAINT ON (person:Person) ASSERT person.id IS UNIQUE
CREATE CONSTRAINT ON (movie:Movie) ASSERT movie.id IS UNIQUE

Now importing the relationships is a matter of finding the nodes and then creating relationships between them.

For this query we’ll use USING PERIODIC COMMIT (see Section 3.3.15, “USING PERIODIC COMMIT”) which is helpful for queries that operate on large CSV files. This hint tells Neo4j that the query might build up inordinate amounts of transaction state, and so needs to be periodically committed. In this case we also set the limit to 500 rows per commit.

USING PERIODIC COMMIT 500
LOAD CSV WITH HEADERS FROM "http://neo4j.com/docs/3.0.7/csv/import/roles.csv" AS csvLine
MATCH (person:Person { id: toInt(csvLine.personId)}),(movie:Movie { id: toInt(csvLine.movieId)})
CREATE (person)-[:PLAYED { role: csvLine.role }]->(movie)

roles.csv. 

personId,movieId,role
1,1,Bud Fox
4,1,Carl Fox
3,1,Gordon Gekko
4,2,A.J. MacInerney
3,2,President Andrew Shepherd
5,3,Ellis Boyd 'Red' Redding

Finally, as the id property was only necessary to import the relationships, we can drop the constraints and the id property from all movie and person nodes.

DROP CONSTRAINT ON (person:Person) ASSERT person.id IS UNIQUE
DROP CONSTRAINT ON (movie:Movie) ASSERT movie.id IS UNIQUE
MATCH (n)
WHERE n:Person OR n:Movie
REMOVE n.id

3.3.15. USING PERIODIC COMMIT

See Section 3.3.14, “Importing CSV files with Cypher” on how to import data from CSV files.

Importing large amounts of data using LOAD CSV with a single Cypher query may fail due to memory constraints. This will manifest itself as an OutOfMemoryError.

For this situation only, Cypher provides the global USING PERIODIC COMMIT query hint for updating queries using LOAD CSV. You can optionally set the limit for the number of rows per commit like so: USING PERIODIC COMMIT 500.

PERIODIC COMMIT will process the rows until the number of rows reaches a limit. Then the current transaction will be committed and replaced with a newly opened transaction. If no limit is set, a default value will be used.

See Section 3.3.6.5, “Importing large amounts of data” in Section 3.3.6, “LOAD CSV” for examples of USING PERIODIC COMMIT with and without setting the number of rows per commit.

Using PERIODIC COMMIT will prevent running out of memory when importing large amounts of data. However, it will also break transactional isolation and thus it should only be used where needed.

3.3.16. RETURN

The RETURN clause defines what to include in the query result set.

In the RETURN part of your query, you define which parts of the pattern you are interested in. It can be nodes, relationships, or properties on these.

If what you actually want is the value of a property, make sure to not return the full node/relationship. This will improve performance.

Figure 3.11. Graph
alt

3.3.16.1. Return nodes

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

Query. 

MATCH (n { name: "B" })
RETURN n

The example will return the node.

Result. 

+-------------------+
| n                 |
+-------------------+
| Node[1]{name:"B"} |
+-------------------+
1 row

3.3.16.2. Return relationships

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

Query. 

MATCH (n { name: "A" })-[r:KNOWS]->(c)
RETURN r

The relationship is returned by the example.

Result. 

+-------------+
| r           |
+-------------+
| :KNOWS[0]{} |
+-------------+
1 row

3.3.16.3. Return property

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

Query. 

MATCH (n { name: "A" })
RETURN n.name

The value of the property name gets returned.

Result. 

+--------+
| n.name |
+--------+
| "A"    |
+--------+
1 row

3.3.16.4. Return all elements

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

Query. 

MATCH p=(a { name: "A" })-[r]->(b)
RETURN *

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

Result. 

+---------------------------------------------------------------------------------------------------------------------------------------------------+
| a                                     | b                 | p                                                                      | r            |
+---------------------------------------------------------------------------------------------------------------------------------------------------+
| Node[0]{name:"A",happy:"Yes!",age:55} | Node[1]{name:"B"} | [Node[0]{name:"A",happy:"Yes!",age:55},:BLOCKS[1]{},Node[1]{name:"B"}] | :BLOCKS[1]{} |
| Node[0]{name:"A",happy:"Yes!",age:55} | Node[1]{name:"B"} | [Node[0]{name:"A",happy:"Yes!",age:55},:KNOWS[0]{},Node[1]{name:"B"}]  | :KNOWS[0]{}  |
+---------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows

3.3.16.5. Variable 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 variable, like this:

Query. 

MATCH (`This isn't a common variable`)
WHERE `This isn't a common variable`.name='A'
RETURN `This isn't a common variable`.happy

The node with name "A" is returned

Result. 

+--------------------------------------+
| `This isn't a common variable`.happy |
+--------------------------------------+
| "Yes!"                               |
+--------------------------------------+
1 row

3.3.16.6. 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. 

MATCH (a { name: "A" })
RETURN a.age AS SomethingTotallyDifferent

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

Result. 

+---------------------------+
| SomethingTotallyDifferent |
+---------------------------+
| 55                        |
+---------------------------+
1 row

3.3.16.7. Optional properties

If a property might or might not be there, you can still select it as usual. It will be treated as NULL if it is missing

Query. 

MATCH (n)
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  |
+--------+
| 55     |
| <null> |
+--------+
2 rows

3.3.16.8. Other expressions

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

Query. 

MATCH (a { name: "A" })
RETURN a.age > 30, "I'm a literal",(a)-->()

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

Result. 

+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| a.age > 30 | "I'm a literal" | (a)-->()                                                                                                                                       |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| true       | "I'm a literal" | [[Node[0]{name:"A",happy:"Yes!",age:55},:BLOCKS[1]{},Node[1]{name:"B"}],[Node[0]{name:"A",happy:"Yes!",age:55},:KNOWS[0]{},Node[1]{name:"B"}]] |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row

3.3.16.9. Unique results

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

Query. 

MATCH (a { name: "A" })-->(b)
RETURN DISTINCT b

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

Result. 

+-------------------+
| b                 |
+-------------------+
| Node[1]{name:"B"} |
+-------------------+
1 row

3.3.17. ORDER BY

ORDER BY is a sub-clause following RETURN or WITH, and it specifies that the output should be sorted and how.

Note that you can not sort on nodes or relationships, just on properties on these. ORDER BY relies on comparisons to sort the output, see Section 3.2.5.8, “Ordering and Comparison of Values”.

In terms of scope of variables, ORDER BY follows special rules, depending on if the projecting RETURN or WITH clause is either aggregating or DISTINCT. If it is an aggregating or DISTINCT projection, only the variables available in the projection are available. If the projection does not alter the output cardinality (which aggregation and DISTINCT do), variables available from before the projecting clause are also available. When the projection clause shadows already existing variables, only the new variables are available.

Lastly, it is not allowed to use aggregating expressions in the ORDER BY sub-clause if they are not also listed in the projecting clause. This last rule is to make sure that ORDER BY does not change the results, only the order of them.

Figure 3.12. Graph
alt

3.3.17.1. Order nodes by property

ORDER BY is used to sort the output.

Query. 

MATCH (n)
RETURN n
ORDER BY n.name

The nodes are returned, sorted by their name.

Result. 

+-------------------------------------+
| n                                   |
+-------------------------------------+
| Node[0]{name:"A",age:34,length:170} |
| Node[1]{name:"B",age:34}            |
| Node[2]{name:"C",age:32,length:185} |
+-------------------------------------+
3 rows

3.3.17.2. Order nodes by multiple properties

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

Query. 

MATCH (n)
RETURN n
ORDER BY n.age, n.name

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

Result. 

+-------------------------------------+
| n                                   |
+-------------------------------------+
| Node[2]{name:"C",age:32,length:185} |
| Node[0]{name:"A",age:34,length:170} |
| Node[1]{name:"B",age:34}            |
+-------------------------------------+
3 rows

3.3.17.3. Order nodes in descending order

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

Query. 

MATCH (n)
RETURN n
ORDER BY n.name DESC

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

Result. 

+-------------------------------------+
| n                                   |
+-------------------------------------+
| Node[2]{name:"C",age:32,length:185} |
| Node[1]{name:"B",age:34}            |
| Node[0]{name:"A",age:34,length:170} |
+-------------------------------------+
3 rows

3.3.17.4. 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. 

MATCH (n)
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                                   |
+------------------------------------------------+
| 170      | Node[0]{name:"A",age:34,length:170} |
| 185      | Node[2]{name:"C",age:32,length:185} |
| <null>   | Node[1]{name:"B",age:34}            |
+------------------------------------------------+
3 rows

3.3.18. LIMIT

LIMIT constrains the number of rows in the output.

LIMIT accepts any expression that evaluates to a positive integer — however the expression cannot refer to nodes or relationships.

Figure 3.13. Graph
alt

3.3.18.1. Return first part

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

Query. 

MATCH (n)
RETURN n
ORDER BY n.name
LIMIT 3

The top three items are returned by the example query.

Result. 

+-------------------+
| n                 |
+-------------------+
| Node[0]{name:"A"} |
| Node[1]{name:"B"} |
| Node[2]{name:"C"} |
+-------------------+
3 rows

3.3.18.2. Return first from expression

Limit accepts any expression that evaluates to a positive integer as long as it is not referring to any external variables:

Parameters. 

{
  "p" : 12
}

Query. 

MATCH (n)
RETURN n
ORDER BY n.name
LIMIT toInt(3 * rand())+ 1

Returns one to three top items

Result. 

+-------------------+
| n                 |
+-------------------+
| Node[0]{name:"A"} |
| Node[1]{name:"B"} |
+-------------------+
2 rows

3.3.19. SKIP

SKIP defines from which row to start including the rows in the output.

By using SKIP, the result set will get trimmed from the top. Please note that no guarantees are made on the order of the result unless the query specifies the ORDER BY clause. SKIP accepts any expression that evaluates to a positive integer — however the expression cannot refer to nodes or relationships.

Figure 3.14. Graph
alt

3.3.19.1. Skip first three

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

Query. 

MATCH (n)
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                 |
+-------------------+
| Node[3]{name:"D"} |
| Node[4]{name:"E"} |
+-------------------+
2 rows

3.3.19.2. Return middle two

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

Query. 

MATCH (n)
RETURN n
ORDER BY n.name
SKIP 1
LIMIT 2

Two nodes from the middle are returned.

Result. 

+-------------------+
| n                 |
+-------------------+
| Node[1]{name:"B"} |
| Node[2]{name:"C"} |
+-------------------+
2 rows

3.3.19.3. Skip first from expression

Skip accepts any expression that evaluates to a positive integer as long as it is not referring to any external variables:

Query. 

MATCH (n)
RETURN n
ORDER BY n.name
SKIP toInt(3*rand())+ 1

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

Result. 

+-------------------+
| n                 |
+-------------------+
| Node[2]{name:"C"} |
| Node[3]{name:"D"} |
| Node[4]{name:"E"} |
+-------------------+
3 rows

3.3.20. WITH

The WITH clause allows query parts to be chained together, piping the results from one to be used as starting points or criteria in the next.

Using WITH, you can manipulate the output before it is passed on to the following query parts. The manipulations can be of the shape and/or number of entries in the result set.

One common usage of WITH is to limit the number of entries that are then passed on to other MATCH clauses. By combining ORDER BY and LIMIT, it’s possible to get the top X entries by some criteria, and then bring in additional data from the graph.

Another use is to filter on aggregated values. WITH is used to introduce aggregates which can then by used in predicates in WHERE. These aggregate expressions create new bindings in the results. WITH can also, like RETURN, alias expressions that are introduced into the results using the aliases as binding name.

WITH is also used to separate reading from updating of the graph. Every part of a query must be either read-only or write-only. When going from a writing part to a reading part, the switch must be done with a WITH clause.

Figure 3.15. Graph
alt

3.3.20.1. Filter on aggregate function results

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

Query. 

MATCH (david { name: "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            |
+------------------------+
| Node[0]{name:"Anders"} |
+------------------------+
1 row

3.3.20.2. Sort results before using collect on them

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

Query. 

MATCH (n)
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 list.

Result. 

+---------------------------+
| collect(n.name)           |
+---------------------------+
| ["Emil","David","Ceasar"] |
+---------------------------+
1 row

3.3.21. UNWIND

UNWIND expands a list into a sequence of rows.

With UNWIND, you can transform any list back into individual rows. These lists can be parameters that were passed in, previously COLLECTed result or other list expressions.

One common usage of unwind is to create distinct lists. Another is to create data from parameter lists that are provided to the query.

UNWIND requires you to specify a new name for the inner values.

3.3.21.1. Unwind a list

We want to transform the literal list into rows named x and return them.

Query. 

UNWIND[1,2,3] AS x
RETURN x

Each value of the original list is returned as an individual row.

Result. 

+---+
| x |
+---+
| 1 |
| 2 |
| 3 |
+---+
3 rows

3.3.21.2. Create a distinct list

We want to transform a list of duplicates into a set using DISTINCT.

Query. 

WITH [1,1,2,2] AS coll UNWIND coll AS x
WITH DISTINCT x
RETURN collect(x) AS SET

Each value of the original list is unwound and passed through DISTINCT to create a unique set.

Result. 

+-------+
| set   |
+-------+
| [1,2] |
+-------+
1 row

3.3.21.3. Create nodes from a list parameter

Create a number of nodes and relationships from a parameter-list without using FOREACH.

Parameters. 

{
  "events" : [ {
    "year" : 2014,
    "id" : 1
  }, {
    "year" : 2014,
    "id" : 2
  } ]
}

Query. 

UNWIND { events } AS event
MERGE (y:Year { year:event.year })
MERGE (y)<-[:IN]-(e:Event { id:event.id })
RETURN e.id AS x
ORDER BY x

Each value of the original list is unwound and passed through MERGE to find or create the nodes and relationships.

Result. 

+---+
| x |
+---+
| 1 |
| 2 |
+---+
2 rows
Nodes created: 3
Relationships created: 2
Properties set: 3
Labels added: 3

3.3.22. UNION

The UNION clause is used to combine the result of multiple queries.

It combines the results of two or more queries into a single result set that includes all the rows that belong to all queries in the union.

The number and the names of the columns must be identical in all queries combined by using UNION.

To keep all the result rows, use UNION ALL. Using just UNION will combine and remove duplicates from the result set.

Figure 3.16. Graph
alt

3.3.22.1. Combine two queries

Combining the results from two queries is done using UNION ALL.

Query. 

MATCH (n:Actor)
RETURN n.name AS name
UNION ALL MATCH (n:Movie)
RETURN n.title AS name

The combined result is returned, including duplicates.

Result. 

+-------------------+
| name              |
+-------------------+
| "Anthony Hopkins" |
| "Helen Mirren"    |
| "Hitchcock"       |
| "Hitchcock"       |
+-------------------+
4 rows

3.3.22.2. Combine two queries and remove duplicates

By not including ALL in the UNION, duplicates are removed from the combined result set

Query. 

MATCH (n:Actor)
RETURN n.name AS name
UNION
MATCH (n:Movie)
RETURN n.title AS name

The combined result is returned, without duplicates.

Result. 

+-------------------+
| name              |
+-------------------+
| "Anthony Hopkins" |
| "Helen Mirren"    |
| "Hitchcock"       |
+-------------------+
3 rows

3.3.23. CALL

The CALL clause is used to call a procedure deployed in the database.

The examples showing how to use arguments when invoking procedures all use the following procedure:

public class IndexingProcedure
{
    @Context
    public GraphDatabaseService db;

    /**
     * Adds a node to a named legacy index. Useful to, for instance, update
     * a full-text index through cypher.
     * @param indexName the name of the index in question
     * @param nodeId id of the node to add to the index
     * @param propKey property to index (value is read from the node)
     */
    @Procedure
    @PerformsWrites
    public void addNodeToIndex( @Name("indexName") String indexName,
                                @Name("node") long nodeId,
                                @Name("propKey" ) String propKey )
    {
        Node node = db.getNodeById( nodeId );
        db.index()
          .forNodes( indexName )
          .add( node, propKey, node.getProperty( propKey ) );
    }
}

This clause cannot be combined with other clauses.

3.3.23.1. Call a procedure

This invokes the built-in procedure 'db.labels', which lists all in-use labels in the database.

Query. 

CALL db.labels

Result. 

+-----------------+
| label           |
+-----------------+
| "User"          |
| "Administrator" |
+-----------------+
2 rows

3.3.23.2. Call a procedure with literal arguments

This invokes the example procedure org.neo4j.procedure.example.addNodeToIndex using arguments that are written out directly in the statement text. This is called literal arguments.

Query. 

CALL org.neo4j.procedure.example.addNodeToIndex('users', 0, 'name')

Since our example procedure does not return any result, the result is empty.

Result. 

+--------------------------------------------+
| No data returned, and nothing was changed. |
+--------------------------------------------+

3.3.23.3. Call a procedure with parameter arguments

This invokes the example procedure org.neo4j.procedure.example.addNodeToIndex using parameters. The procedure arguments are satisfied by matching the parameter keys to the named procedure arguments.

Parameters. 

{
  "indexName" : "users",
  "node" : 0,
  "propKey" : "name"
}

Query. 

CALL org.neo4j.procedure.example.addNodeToIndex

Since our example procedure does not return any result, the result is empty.

Result. 

+--------------------------------------------+
| No data returned, and nothing was changed. |
+--------------------------------------------+

3.3.23.4. Call a procedure with mixed literal and parameter arguments

This invokes the example procedure org.neo4j.procedure.example.addNodeToIndex using both literal and parameterized arguments.

Parameters. 

{
  "node" : 0
}

Query. 

CALL org.neo4j.procedure.example.addNodeToIndex('users', { node }, 'name')

Since our example procedure does not return any result, the result is empty.

Result. 

+--------------------------------------------+
| No data returned, and nothing was changed. |
+--------------------------------------------+

3.3.23.5. Call a procedure within a complex query

This invokes the built-in procedure 'db.labels' to count all in-use labels in the database

Query. 

CALL db.labels() YIELD label
RETURN count(label) AS numLabels

Since the procedure call is part of a larger query, all outputs must be named explicitly

Result. 

+-----------+
| numLabels |
+-----------+
| 2         |
+-----------+
1 row

3.3.23.6. Call a procedure within a complex query and rename its outputs

This invokes the built-in procedure 'db.propertyKeys' as part of counting the number of nodes per property key in-use in the database

Query. 

CALL db.propertyKeys() YIELD propertyKey AS prop
MATCH (n)
WHERE n[prop] IS NOT NULL RETURN prop, count(n) AS numNodes

Since the procedure call is part of a larger query, all outputs must be named explicitly

Result. 

+-------------------+
| prop   | numNodes |
+-------------------+
| "name" | 1        |
+-------------------+
1 row

3.4. Functions

This section contains information on all functions in the Cypher query language.

Note that related information exists in Section 3.2.5, “Operators”.

Most functions in Cypher will return NULL if an input parameter is NULL.

Predicate functions

These functions return either true or false for the arguments given.

Function Description

all()

Tests whether a predicate holds for all elements in the list.

any()

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

exists()

Returns true if a match for the pattern exists in the graph, or the property exists in the node, relationship or map.

none()

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

single()

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

Scalar functions

These functions return a single value.

Function Description

coalesce()

Returns the first non-NULL value in the list of expressions passed to it.

endNode()

Returns the last node of a relationship.

head()

Returns the first element in a list.

id()

Returns the id of the relationship or node.

last()

Returns the last element in a list.

length()

Returns the length of a path.

Length of string

Returns the length of a string.

properties()

If the argument is a node or a relationship, the returned map is a map of its properties.

startNode()

Returns the first node of a relationship.

size()

Returns the number of items in a list.

Size of pattern expression

Returns the number of sub-graphs matching the pattern expression.

timestamp()

Returns the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC.

toInt()

Converts the argument to an integer and returns the result.

toFloat()

Converts the argument to a float and returns the result.

type()

Returns a string representation of the relationship type.

List functions

These functions return lists of other values.

Function Description

extract()

Returns a single property, or the value of a function from a list of nodes or relationships.

filter()

Returns all the elements in a list complying with a predicate.

keys()

Returns a list of string representations for the property names of a node, relationship, or map.

labels()

Returns a list of string representations for the labels attached to a node.

nodes()

Returns all nodes in a path.

range()

Returns numerical values in a range.

reduce()

Runs an expression against individual elements of a list, storing the result of the expression in an accumulator.

relationships()

Returns all relationships in a path.

tail()

Returns all but the first element in a list.

Mathematical functions

These functions all operate on numerical expressions only, and will return an error if used on any other values.

1. Numeric functions

Function Description

abs()

Returns the absolute value of a number.

ceil()

Returns the smallest integer greater than or equal to the argument.

floor()

Returns the greatest integer less than or equal to the expression.

rand()

Returns a random number in the range from 0 (inclusive) to 1 (exclusive), [0,1).

round()

Returns the numerical expression, rounded to the nearest integer.

sign()

Returns the signum of a number — zero if the expression is zero, -1 for any negative number, and 1 for any positive number.

2. Logarithmic functions

Function Description

e()

Returns the base of the natural logarithm, e.

exp()

Returns e^n, where e is the base of the natural logarithm, and n is the value of the argument expression.

log()

Returns the natural logarithm of the expression.

log10()

Returns the common logarithm (base 10) of the expression.

sqrt()

Returns the square root of a number.

3. Trigonometric functions

All trigonometric functions operate on radians, unless otherwise specified.

Function Description

acos()

Returns the arccosine of the expression.

asin()

Returns the arcsine of the expression.

atan()

Returns the arctangent of the expression.

atan2()

Returns the arctangent2 of a set of coordinates.

cos()

Returns the cosine of the expression.

cot()

Returns the cotangent of the expression.

degrees()

Converts radians to degrees.

haversin()

Returns half the versine of the expression.

pi()

Returns the mathematical constant pi.

radians()

Converts degrees to radians.

sin()

Returns the sine of the expression.

tan()

Returns the tangent of the expression.

String functions

These functions are used to manipulate strings or to create a string representation of another value.

Function Description

left()

Returns a string containing the left n characters of the original string.

lower()

Returns the original string in lowercase.

ltrim()

Returns the original string with whitespace removed from the left side.

replace()

Returns a string with the search string replaced by the replace string, replacing all occurrences.

reverse()

Returns the original string reversed.

right()

Returns a string containing the right n characters of the original string.

rtrim()

Returns the original string with whitespace removed from the right side.

split()

Returns the sequence of strings which are delimited by split patterns.

substring()

Returns a substring of the original, with a 0-based index start and length.

toString()

Converts the argument to a string.

trim()

Returns the original string with whitespace removed from both sides.

upper()

Returns the original string in uppercase.

3.4.1. 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.

See also Section 3.2.5.2, “Comparison operators”.

Figure 3.17. Graph
alt

3.4.1.1. all()

Tests whether a predicate holds for all elements of this list.

Syntax: all(variable IN list WHERE predicate)

Arguments:

  • list: An expression that returns a list
  • variable: This is the variable that can be used from the predicate.
  • predicate: A predicate that is tested against all items in the list.

Query. 

MATCH p=(a)-[*1..3]->(b)
WHERE a.name='Alice' AND b.name='Daniel' AND 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                                                                                                                                                          |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| [Node[0]{name:"Alice",age:38,eyes:"brown"},:KNOWS[1]{},Node[2]{name:"Charlie",age:53,eyes:"green"},:KNOWS[3]{},Node[3]{name:"Daniel",age:54,eyes:"brown"}] |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row

3.4.1.2. any()

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

Syntax: any(variable IN list WHERE predicate)

Arguments:

  • list: An expression that returns a list
  • variable: This is the variable that can be used from the predicate.
  • predicate: A predicate that is tested against all items in the list.

Query. 

MATCH (a)
WHERE a.name='Eskil' AND 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                                                                    |
+----------------------------------------------------------------------+
| Node[4]{array:["one","two","three"],name:"Eskil",age:41,eyes:"blue"} |
+----------------------------------------------------------------------+
1 row

3.4.1.3. none()

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

Syntax: none(variable in list WHERE predicate)

Arguments:

  • list: An expression that returns a list
  • variable: This is the variable that can be used from the predicate.
  • predicate: A predicate that is tested against all items in the list.

Query. 

MATCH p=(n)-[*1..3]->(b)
WHERE n.name='Alice' AND 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                                                                                                                                                          |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| [Node[0]{name:"Alice",age:38,eyes:"brown"},:KNOWS[1]{},Node[2]{name:"Charlie",age:53,eyes:"green"}]                                                        |
| [Node[0]{name:"Alice",age:38,eyes:"brown"},:KNOWS[1]{},Node[2]{name:"Charlie",age:53,eyes:"green"},:KNOWS[3]{},Node[3]{name:"Daniel",age:54,eyes:"brown"}] |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows

3.4.1.4. single()

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

Syntax: single(variable in list WHERE predicate)

Arguments:

  • list: An expression that returns a list
  • variable: This is the variable that can be used from the predicate.
  • predicate: A predicate that is tested against all items in the list.

Query. 

MATCH p=(n)-->(b)
WHERE n.name='Alice' AND 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                                                                                              |
+------------------------------------------------------------------------------------------------+
| [Node[0]{name:"Alice",age:38,eyes:"brown"},:KNOWS[0]{},Node[1]{name:"Bob",age:25,eyes:"blue"}] |
+------------------------------------------------------------------------------------------------+
1 row

3.4.1.5. exists()

Returns true if a match for the pattern exists in the graph, or the property exists in the node, relationship or map.

Syntax: exists( pattern-or-property )

Arguments:

  • pattern-or-property: A pattern or a property (in the form 'variable.prop').

Query. 

MATCH (n)
WHERE EXISTS(n.name)
RETURN n.name AS name, EXISTS((n)-[:MARRIED]->()) AS is_married

This query returns all the nodes with a name property along with a boolean true/false indicating if they are married.

Result. 

+------------------------+
| name      | is_married |
+------------------------+
| "Alice"   | false      |
| "Bob"     | true       |
| "Charlie" | false      |
| "Daniel"  | false      |
| "Eskil"   | false      |
+------------------------+
5 rows

3.4.2. Scalar functions

Scalar functions return a single value.

The length() and size() functions are quite similar, and so it is important to take note of the difference. Due to backwards compatibility, length() currently works on four types: strings, paths, lists and pattern expressions. However, for clarity it is recommended to only use length() on strings and paths, and use the new size() function on lists and pattern expressions. length() on those types may be deprecated in future.

Figure 3.18. Graph
alt

3.4.2.1. size()

To return or filter on the size of a list, use the size() function.

Syntax: size( list )

Arguments:

  • list: An expression that returns a list

Query. 

RETURN size(['Alice', 'Bob']) AS col

The number of items in the list is returned by the query.

Result. 

+-----+
| col |
+-----+
| 2   |
+-----+
1 row

3.4.2.2. Size of pattern expression

This is the same size() method described before, but instead of passing in a list directly, you provide a pattern expression that can be used in a match query to provide a new set of results. The size of the result is calculated, not the length of the expression itself.

Syntax: size( pattern expression )

Arguments:

  • pattern expression: A pattern expression that returns a list

Query. 

MATCH (a)
WHERE a.name='Alice'
RETURN size((a)-->()-->()) AS fof

The number of sub-graphs matching the pattern expression is returned by the query.

Result. 

+-----+
| fof |
+-----+
| 3   |
+-----+
1 row

3.4.2.3. length()

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

Syntax: length( path )

Arguments:

  • path: An expression that returns a path

Query. 

MATCH p=(a)-->(b)-->(c)
WHERE a.name='Alice'
RETURN length(p)

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

Result. 

+-----------+
| length(p) |
+-----------+
| 2         |
| 2         |
| 2         |
+-----------+
3 rows

3.4.2.4. Length of string

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

Syntax: length( string )

Arguments:

  • string: An expression that returns a string

Query. 

MATCH (a)
WHERE length(a.name)> 6
RETURN length(a.name)

The length of the name Charlie is returned by the query.

Result. 

+----------------+
| length(a.name) |
+----------------+
| 7              |
+----------------+
1 row

3.4.2.5. type()

Returns a string representation of the relationship type.

Syntax: type( relationship )

Arguments:

  • relationship: A relationship.

Query. 

MATCH (n)-[r]->()
WHERE n.name='Alice'
RETURN type(r)

The relationship type of r is returned by the query.

Result. 

+---------+
| type(r) |
+---------+
| "KNOWS" |
| "KNOWS" |
+---------+
2 rows

3.4.2.6. id()

Returns the id of the relationship or node.

Syntax: id( property-container )

Arguments:

  • property-container: A node or a relationship.

Query. 

MATCH (a)
RETURN id(a)

This returns the node id for three nodes.

Result. 

+-------+
| id(a) |
+-------+
| 0     |
| 1     |
| 2     |
| 3     |
| 4     |
+-------+
5 rows

3.4.2.7. coalesce()

Returns the first non-NULL value in the list of expressions passed to it. In case all arguments are NULL, NULL will be returned.

Syntax: coalesce( expression [, expression]* )

Arguments:

  • expression: The expression that might return NULL.

Query. 

MATCH (a)
WHERE a.name='Alice'
RETURN coalesce(a.hairColor, a.eyes)

Result. 

+-------------------------------+
| coalesce(a.hairColor, a.eyes) |
+-------------------------------+
| "brown"                       |
+-------------------------------+
1 row

3.4.2.8. head()

head() returns the first element in a list.

Syntax: head( expression )

Arguments:

  • expression: This expression should return a list of some kind.

Query. 

MATCH (a)
WHERE a.name='Eskil'
RETURN a.array, head(a.array)

The first node in the path is returned.

Result. 

+---------------------------------------+
| a.array               | head(a.array) |
+---------------------------------------+
| ["one","two","three"] | "one"         |
+---------------------------------------+
1 row

3.4.2.9. last()

last() returns the last element in a list.

Syntax: last( expression )

Arguments:

  • expression: This expression should return a list of some kind.

Query. 

MATCH (a)
WHERE a.name='Eskil'
RETURN a.array, last(a.array)

The last node in the path is returned.

Result. 

+---------------------------------------+
| a.array               | last(a.array) |
+---------------------------------------+
| ["one","two","three"] | "three"       |
+---------------------------------------+
1 row

3.4.2.10. timestamp()

timestamp() returns the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC. It will return the same value during the whole one query, even if the query is a long running one.

Syntax: timestamp()

Arguments:

Query. 

RETURN timestamp()

The time in milliseconds is returned.

Result. 

+---------------+
| timestamp()   |
+---------------+
| 1480347930425 |
+---------------+
1 row

3.4.2.11. startNode()

startNode() returns the starting node of a relationship

Syntax: startNode( relationship )

Arguments:

  • relationship: An expression that returns a relationship

Query. 

MATCH (x:foo)-[r]-()
RETURN startNode(r)

Result. 

+-------------------------------------------+
| startNode(r)                              |
+-------------------------------------------+
| Node[0]{name:"Alice",age:38,eyes:"brown"} |
| Node[0]{name:"Alice",age:38,eyes:"brown"} |
+-------------------------------------------+
2 rows

3.4.2.12. endNode()

endNode() returns the end node of a relationship

Syntax: endNode( relationship )

Arguments:

  • relationship: An expression that returns a relationship

Query. 

MATCH (x:foo)-[r]-()
RETURN endNode(r)

Result. 

+---------------------------------------------+
| endNode(r)                                  |
+---------------------------------------------+
| Node[2]{name:"Charlie",age:53,eyes:"green"} |
| Node[1]{name:"Bob",age:25,eyes:"blue"}      |
+---------------------------------------------+
2 rows

3.4.2.13. properties()

properties() converts the arguments to a map of its properties. If the argument is a node or a relationship, the returned map is a map of its properties .If the argument is already a map, it is returned unchanged.

Syntax: properties( expression )

Arguments:

  • expression: An expression that returns a node, a relationship, or a map

Query. 

CREATE (p:Person { name: 'Stefan', city: 'Berlin' })
RETURN properties(p)

Result. 

+--------------------------------------+
| properties(p)                        |
+--------------------------------------+
| {name -> "Stefan", city -> "Berlin"} |
+--------------------------------------+
1 row
Nodes created: 1
Properties set: 2
Labels added: 1

3.4.2.14. toInt()

toInt() converts the argument to an integer. A string is parsed as if it was an integer number. If the parsing fails, NULL will be returned. A floating point number will be cast into an integer.

Syntax: toInt( expression )

Arguments:

  • expression: An expression that returns anything

Query. 

RETURN toInt("42"), toInt("not a number")

Result. 

+-------------------------------------+
| toInt("42") | toInt("not a number") |
+-------------------------------------+
| 42          | <null>                |
+-------------------------------------+
1 row

3.4.2.15. toFloat()

toFloat() converts the argument to a float. A string is parsed as if it was an floating point number. If the parsing fails, NULL will be returned. An integer will be cast to a floating point number.

Syntax: toFloat( expression )

Arguments:

  • expression: An expression that returns anything

Query. 

RETURN toFloat("11.5"), toFloat("not a number")

Result. 

+-------------------------------------------+
| toFloat("11.5") | toFloat("not a number") |
+-------------------------------------------+
| 11.5            | <null>                  |
+-------------------------------------------+
1 row

3.4.3. List functions

List functions return lists of things — nodes in a path, and so on.

See also Section 3.2.5.5, “List operators”.

Figure 3.19. Graph
alt

3.4.3.1. nodes()

Returns all nodes in a path.

Syntax: nodes( path )

Arguments:

  • path: A path.

Query. 

MATCH p=(a)-->(b)-->(c)
WHERE a.name='Alice' AND c.name='Eskil'
RETURN nodes(p)

All the nodes in the path p are returned by the example query.

Result. 

+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| nodes(p)                                                                                                                                                |
+---------------------------------------------------------------------------------------------------------------------------------------------------------+
| [Node[0]{name:"Alice",age:38,eyes:"brown"},Node[1]{name:"Bob",age:25,eyes:"blue"},Node[4]{array:["one","two","three"],name:"Eskil",age:41,eyes:"blue"}] |
+---------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row

3.4.3.2. relationships()

Returns all relationships in a path.

Syntax: relationships( path )

Arguments:

  • path: A path.

Query. 

MATCH p=(a)-->(b)-->(c)
WHERE a.name='Alice' AND c.name='Eskil'
RETURN relationships(p)

All the relationships in the path p are returned.

Result. 

+-----------------------------+
| relationships(p)            |
+-----------------------------+
| [:KNOWS[0]{},:MARRIED[4]{}] |
+-----------------------------+
1 row

3.4.3.3. labels()

Returns a list of string representations for the labels attached to a node.

Syntax: labels( node )

Arguments:

  • node: Any expression that returns a single node

Query. 

MATCH (a)
WHERE a.name='Alice'
RETURN labels(a)

The labels of n is returned by the query.

Result. 

+---------------+
| labels(a)     |
+---------------+
| ["bar","foo"] |
+---------------+
1 row

3.4.3.4. keys()

Returns a list of string representations for the property names of a node, relationship, or map.

Syntax: keys( property-container )

Arguments:

  • property-container: A node, a relationship, or a literal map.

Query. 

MATCH (a)
WHERE a.name='Alice'
RETURN keys(a)

The name of the properties of n is returned by the query.

Result. 

+-----------------------+
| keys(a)               |
+-----------------------+
| ["name","age","eyes"] |
+-----------------------+
1 row

3.4.3.5. extract()

To return a single property, or the value of a function from a list of nodes or relationships, you can use extract(). It will go through a list, run an expression on every element, and return the results in a list with these values. It works like the map method in functional languages such as Lisp and Scala.

Syntax: extract( variable IN list | expression )

Arguments:

  • list: An expression that returns a list
  • variable: The closure will have a variable introduced in it’s context. Here you decide which variable to use.
  • expression: This expression will run once per value in the list, and produces the result list.

Query. 

MATCH p=(a)-->(b)-->(c)
WHERE a.name='Alice' AND b.name='Bob' AND c.name='Daniel'
RETURN extract(n IN nodes(p)| n.age) AS extracted

The age property of all nodes in the path are returned.

Result. 

+------------+
| extracted  |
+------------+
| [38,25,54] |
+------------+
1 row

3.4.3.6. filter()

filter() returns all the elements in a list that comply to a predicate.

Syntax: filter(variable IN list WHERE predicate)

Arguments:

  • list: An expression that returns a list
  • variable: This is the variable that can be used from the predicate.
  • predicate: A predicate that is tested against all items in the list.

Query. 

MATCH (a)
WHERE a.name='Eskil'
RETURN a.array, filter(x IN a.array WHERE size(x)= 3)

This returns the property named array and a list of values in it, which have size 3.

Result. 

+----------------------------------------------------------------+
| a.array               | filter(x in a.array WHERE size(x) = 3) |
+----------------------------------------------------------------+
| ["one","two","three"] | ["one","two"]                          |
+----------------------------------------------------------------+
1 row

3.4.3.7. tail()

tail() returns all but the first element in a list.

Syntax: tail( expression )

Arguments:

  • expression: This expression should return a list of some kind.

Query. 

MATCH (a)
WHERE a.name='Eskil'
RETURN a.array, tail(a.array)

This returns the property named array and all elements of that property except the first one.

Result. 

+-----------------------------------------+
| a.array               | tail(a.array)   |
+-----------------------------------------+
| ["one","two","three"] | ["two","three"] |
+-----------------------------------------+
1 row

3.4.3.8. range()

range() returns numerical values in a range. The default distance between values in the range is 1. The r is inclusive in both ends.

Syntax: range( start, end [, step] )

Arguments:

  • start: A numerical expression.
  • end: A numerical expression.
  • step: A numerical expression.

Query. 

RETURN range(0,10), range(2,18,3)

Two lists of numbers in the given ranges are returned.

Result. 

+---------------------------------------------+
| range(0,10)              | range(2,18,3)    |
+---------------------------------------------+
| [0,1,2,3,4,5,6,7,8,9,10] | [2,5,8,11,14,17] |
+---------------------------------------------+
1 row

3.4.3.9. reduce()

To run an expression against individual elements of a list, and store the result of the expression in an accumulator, you can use reduce(). It will go through a list, 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, variable IN list | expression )

Arguments:

  • accumulator: A variable that will hold the result and the partial results as the list is iterated
  • initial: An expression that runs once to give a starting value to the accumulator
  • list: An expression that returns a list
  • variable: The closure will have a variable introduced in its context. Here you decide which variable to use.
  • expression: This expression will run once per value in the list, and produces the result value.

Query. 

MATCH p=(a)-->(b)-->(c)
WHERE a.name='Alice' AND b.name='Bob' AND c.name='Daniel'
RETURN reduce(totalAge = 0, n IN nodes(p)| totalAge + n.age) AS reduction

The age property of all nodes in the path are summed and returned as a single value.

Result. 

+-----------+
| reduction |
+-----------+
| 117       |
+-----------+
1 row

3.4.4. Mathematical functions

These functions all operate on numerical expressions only, and will return an error if used on any other values. See also Section 3.2.5.1, “Mathematical operators”..

The following graph is used for the examples below:

Figure 3.20. Graph
alt

3.4.4.1. Number functions

abs()

abs() returns the absolute value of a number.

Syntax: abs( expression )

Arguments:

  • expression: A numeric expression.

Query. 

MATCH (a),(e)
WHERE a.name = 'Alice' AND e.name = 'Eskil'
RETURN a.age, e.age, abs(a.age - e.age)

The absolute value of the age difference is returned.

Table 3.24. Result
a.age e.age abs(a.age - e.age)

1 row

38

41

3

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) MATCH (a), (e) WHERE a.name = 'Alice' AND e.name = 'Eskil' RETURN a.age, e.age, abs(a.age - e.age)

ceil()

ceil() returns the smallest integer greater than or equal to the argument.

Syntax: ceil( expression )

Arguments:

  • expression: A numeric expression.

Query. 

RETURN ceil(0.1)

The ceil of 0.1.

Table 3.25. Result
ceil(0.1)

1 row

1.0

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN ceil(0.1)

floor()

floor() returns the greatest integer less than or equal to the expression.

Syntax: floor( expression )

Arguments:

  • expression: A numeric expression.

Query. 

RETURN floor(0.9)

The floor of 0.9 is returned.

Table 3.26. Result
floor(0.9)

1 row

0.0

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN floor(0.9)

round()

round() returns the numerical expression, rounded to the nearest integer.

Syntax: round( expression )

Arguments:

  • expression: A numeric expression that represents the angle in radians.

Query. 

RETURN round(3.141592)

3.0 is returned.

Table 3.27. Result
round(3.141592)

1 row

3.0

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN round(3.141592)

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 numeric expression.

Query. 

RETURN sign(-17), sign(0.1)

The signs of -17 and 0.1 are returned.

Table 3.28. Result
sign(-17) sign(0.1)

1 row

-1

1

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN sign(-17), sign(0.1)

rand()

rand() returns a random number in the range from 0 (inclusive) to 1 (exclusive), [0,1). The numbers returned follow an approximate uniform distribution.

Syntax: rand()

Arguments:

Query. 

RETURN rand()

A random number is returned.

Table 3.29. Result
rand()

1 row

0.04385866719728437

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN rand()

3.4.4.2. Logarithmic functions

log()

log() returns the natural logarithm of the expression.

Syntax: log( expression )

Arguments:

  • expression: A numeric expression.

Query. 

RETURN log(27)

The natural logarithm of 27 is returned.

Table 3.30. Result
log(27)

1 row

3.295836866004329

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN log(27)

log10()

log10() returns the common logarithm (base 10) of the expression.

Syntax: log10( expression )

Arguments:

  • expression: A numeric expression.

Query. 

RETURN log10(27)

The common logarithm of 27 is returned.

Table 3.31. Result
log10(27)

1 row

1.4313637641589874

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN log10(27)

exp()

exp() returns e^n, where e is the base of the natural logarithm, and n is the value of the argument expression.

Syntax: e( expression )

Arguments:

  • expression: A numeric expression.

Query. 

RETURN exp(2)

e to the power of 2 is returned.

Table 3.32. Result
exp(2)

1 row

7.38905609893065

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN exp(2)

e()

e() returns the base of the natural logarithm, e.

Syntax: e()

Arguments:

Query. 

RETURN e()

The base of the natural logarithm, e, is returned.

Table 3.33. Result
e()

1 row

2.718281828459045

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN e()

sqrt()

sqrt() returns the square root of a number.

Syntax: sqrt( expression )

Arguments:

  • expression: A numeric expression.

Query. 

RETURN sqrt(256)

The square root of 256 is returned.

Table 3.34. Result
sqrt(256)

1 row

16.0

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN sqrt(256)

3.4.4.3. Trigonometric functions

All trigonometric functions operate on radians, unless otherwise specified.

sin()

sin() returns the sine of the expression.

Syntax: sin( expression )

Arguments:

  • expression: A numeric expression that represents the angle in radians.

Query. 

RETURN sin(0.5)

The sine of 0.5 is returned.

Table 3.35. Result
sin(0.5)

1 row

0.479425538604203

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN sin(0.5)

cos()

cos() returns the cosine of the expression.

Syntax: cos( expression )

Arguments:

  • expression: A numeric expression that represents the angle in radians.

Query. 

RETURN cos(0.5)

The cosine of 0.5.

Table 3.36. Result
cos(0.5)

1 row

0.8775825618903728

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN cos(0.5)

tan()

tan() returns the tangent of the expression.

Syntax: tan( expression )

Arguments:

  • expression: A numeric expression that represents the angle in radians.

Query. 

RETURN tan(0.5)

The tangent of 0.5 is returned.

Table 3.37. Result
tan(0.5)

1 row

0.5463024898437905

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN tan(0.5)

cot()

cot() returns the cotangent of the expression.

Syntax: cot( expression )

Arguments:

  • expression: A numeric expression that represents the angle in radians.

Query. 

RETURN cot(0.5)

The cotangent of 0.5.

Table 3.38. Result
cot(0.5)

1 row

1.830487721712452

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN cot(0.5)

asin()

asin() returns the arcsine of the expression, in radians.

Syntax: asin( expression )

Arguments:

  • expression: A numeric expression that represents the angle in radians.

Query. 

RETURN asin(0.5)

The arcsine of 0.5.

Table 3.39. Result
asin(0.5)

1 row

0.5235987755982989

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN asin(0.5)

acos()

acos() returns the arccosine of the expression, in radians.

Syntax: abs( expression )

Arguments:

  • expression: A numeric expression that represents the angle in radians.

Query. 

RETURN acos(0.5)

The arccosine of 0.5.

Table 3.40. Result
acos(0.5)

1 row

1.0471975511965979

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN acos(0.5)

atan()

atan() returns the arctangent of the expression, in radians.

Syntax: atan( expression )

Arguments:

  • expression: A numeric expression that represents the angle in radians.

Query. 

RETURN atan(0.5)

The arctangent of 0.5.

Table 3.41. Result
atan(0.5)

1 row

0.4636476090008061

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN atan(0.5)

atan2()

atan2() returns the arctangent2 of a set of coordinates, in radians.

Syntax: atan2( expression1, expression2 )

Arguments:

  • expression1: A numeric expression for y that represents the angle in radians.
  • expression2: A numeric expression for x that represents the angle in radians.

Query. 

RETURN atan2(0.5, 0.6)

The arctangent2 of 0.5 and 0.6.

Table 3.42. Result
atan2(0.5, 0.6)

1 row

0.6947382761967033

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN atan2(0.5, 0.6)

pi()

pi() returns the mathematical constant pi.

Syntax: pi()

Arguments:

Query. 

RETURN pi()

The constant pi is returned.

Table 3.43. Result
pi()

1 row

3.141592653589793

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN pi()

degrees()

degrees() converts radians to degrees.

Syntax: degrees( expression )

Arguments:

  • expression: A numeric expression that represents the angle in radians.

Query. 

RETURN degrees(3.14159)

The number of degrees in something close to pi.

Table 3.44. Result
degrees(3.14159)

1 row

179.99984796050427

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN degrees(3.14159)

radians()

radians() converts degrees to radians.

Syntax: radians( expression )

Arguments:

  • expression: A numeric expression that represents the angle in degrees.

Query. 

RETURN radians(180)

The number of radians in 180 degrees is returned (pi).

Table 3.45. Result
radians(180)

1 row

3.141592653589793

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN radians(180)

haversin()

haversin() returns half the versine of the expression.

Syntax: haversin( expression )

Arguments:

  • expression: A numeric expression that represents the angle in radians.

Query. 

RETURN haversin(0.5)

The haversine of 0.5 is returned.

Table 3.46. Result
haversin(0.5)

1 row

0.06120871905481362

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) RETURN haversin(0.5)

Spherical distance using the haversin function

The haversin() function may be used to compute the distance on the surface of a sphere between two points (each given by their latitude and longitude). In this example the spherical distance (in km) between Berlin in Germany (at lat 52.5, lon 13.4) and San Mateo in California (at lat 37.5, lon -122.3) is calculated using an average earth radius of 6371 km.

Query. 

CREATE (ber:City { lat: 52.5, lon: 13.4 }),(sm:City { lat: 37.5, lon: -122.3 })
RETURN 2 * 6371 * asin(sqrt(haversin(radians(sm.lat - ber.lat))+ cos(radians(sm.lat))* cos(radians(ber.lat))* haversin(radians(sm.lon - ber.lon)))) AS dist

The estimated distance between Berlin and San Mateo is returned.

Table 3.47. Result
dist

1 row Nodes created: 2 Properties set: 4 Labels added: 2

9129.969740051658

Try this query live.  CREATE (alice:A {name:'Alice', age: 38, eyes: 'brown'}), (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), (daniel:D {name: 'Daniel', age: 54, eyes: 'brown'}), (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), (alice)-[:KNOWS]->(bob), (alice)-[:KNOWS]->(charlie), (bob)-[:KNOWS]->(daniel), (charlie)-[:KNOWS]->(daniel), (bob)-[:MARRIED]->(eskil) CREATE (ber:City {lat: 52.5, lon: 13.4}), (sm:City {lat: 37.5, lon: -122.3}) RETURN 2 * 6371 * asin(sqrt(haversin(radians( sm.lat - ber.lat )) + cos(radians( sm.lat )) * cos(radians( ber.lat )) * haversin(radians( sm.lon - ber.lon )))) AS dist

3.4.5. String functions

These functions all operate on string expressions only, and will return an error if used on any other values. The exception to this rule is toString(), which also accepts numbers and booleans.

See also Section 3.2.5.4, “String operators”.

Figure 3.21. Graph
alt

3.4.5.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. 

RETURN replace("hello", "l", "w")

Result. 

+----------------------------+
| replace("hello", "l", "w") |
+----------------------------+
| "hewwo"                    |
+----------------------------+
1 row

3.4.5.2. 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. 

RETURN substring("hello", 1, 3), substring("hello", 2)

Result. 

+--------------------------------------------------+
| substring("hello", 1, 3) | substring("hello", 2) |
+--------------------------------------------------+
| "ell"                    | "llo"                 |
+--------------------------------------------------+
1 row

3.4.5.3. 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. 

RETURN left("hello", 3)

Result. 

+------------------+
| left("hello", 3) |
+------------------+
| "hel"            |
+------------------+
1 row

3.4.5.4. 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. 

RETURN right("hello", 3)

Result. 

+-------------------+
| right("hello", 3) |
+-------------------+
| "llo"             |
+-------------------+
1 row

3.4.5.5. 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. 

RETURN ltrim("   hello")

Result. 

+-------------------+
| ltrim("   hello") |
+-------------------+
| "hello"           |
+-------------------+
1 row

3.4.5.6. 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. 

RETURN rtrim("hello   ")

Result. 

+-------------------+
| rtrim("hello   ") |
+-------------------+
| "hello"           |
+-------------------+
1 row

3.4.5.7. trim()

trim() returns the original string with whitespace removed from both sides.

Syntax: trim( original )

Arguments:

  • original: An expression that returns a string

Query. 

RETURN trim("   hello   ")

Result. 

+---------------------+
| trim("   hello   ") |
+---------------------+
| "hello"             |
+---------------------+
1 row

3.4.5.8. lower()

lower() returns the original string in lowercase.

Syntax: lower( original )

Arguments:

  • original: An expression that returns a string

Query. 

RETURN lower("HELLO")

Result. 

+----------------+
| lower("HELLO") |
+----------------+
| "hello"        |
+----------------+
1 row

3.4.5.9. upper()

upper() returns the original string in uppercase.

Syntax: upper( original )

Arguments:

  • original: An expression that returns a string

Query. 

RETURN upper("hello")

Result. 

+----------------+
| upper("hello") |
+----------------+
| "HELLO"        |
+----------------+
1 row

3.4.5.10. split()

split() returns the sequence of strings which are delimited by split patterns.

Syntax: split( original, splitPattern )

Arguments:

  • original: An expression that returns a string
  • splitPattern: The string to split the original string with

Query. 

RETURN split("one,two", ",")

Result. 

+-----------------------+
| split("one,two", ",") |
+-----------------------+
| ["one","two"]         |
+-----------------------+
1 row

3.4.5.11. reverse()

reverse() returns the original string reversed.

Syntax: reverse( original )

Arguments:

  • original: An expression that returns a string

Query. 

RETURN reverse("anagram")

Result. 

+--------------------+
| reverse("anagram") |
+--------------------+
| "margana"          |
+--------------------+
1 row

3.4.5.12. toString()

toString() converts the argument to a string. It converts integral and floating point numbers and booleans to strings, and if called with a string will leave it unchanged.

Syntax: toString( expression )

Arguments:

  • expression: An expression that returns a number, a boolean, or a string

Query. 

RETURN toString(11.5), toString("already a string"), toString(TRUE )

Result. 

+----------------------------------------------------------------+
| toString(11.5) | toString("already a string") | toString(true) |
+----------------------------------------------------------------+
| "11.5"         | "already a string"           | "true"         |
+----------------------------------------------------------------+
1 row

3.5. Schema

This section explains how to work with an optional schema in Neo4j in the Cypher query language.

Neo4j 2.0 introduced an optional schema for the graph, based around the concept of labels. Labels are used in the specification of indexes, and for defining constraints on the graph. Together, indexes and constraints are the schema of the graph. Cypher includes data definition language (DDL) statements for manipulating the schema.

3.5.1. Indexes

A database index is a redundant copy of information in the database for the purpose of making retrieving said data more efficient. This comes at the cost of additional storage space and slower writes, so deciding what to index and what not to index is an important and often non-trivial task.

Cypher allows the creation of indexes over a property for all nodes that have a given label. Once an index has been created, it will automatically be managed and kept up to date by the database whenever the graph is changed. Neo4j will automatically pick up and start using the index once it has been created and brought online.

3.5.1.1. Create an index

To create an index on a property for all nodes that have a label, use CREATE INDEX ON. Note that the index is not immediately available, but will be created in the background.

Query. 

CREATE INDEX ON :Person(name)

Result. 

+--------------------------------------------+
| No data returned, and nothing was changed. |
+--------------------------------------------+

3.5.1.2. Drop an index

To drop an index on all nodes that have a label and property combination, use the DROP INDEX clause.

Query. 

DROP INDEX ON :Person(name)

Result. 

+-------------------+
| No data returned. |
+-------------------+
Indexes removed: 1

3.5.1.3. Use index

There is usually no need to specify which indexes to use in a query, Cypher will figure that out by itself. For example the query below will use the Person(name) index, if it exists. If you want Cypher to use specific indexes, you can enforce it using hints. See Section 3.6.4, “USING”.

Query. 

MATCH (person:Person { name: 'Andres' })
RETURN person

Query Plan. 

+-----------------+----------------+------+---------+-----------+---------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables | Other         |
+-----------------+----------------+------+---------+-----------+---------------+
| +ProduceResults |              1 |    1 |       0 | person    | person        |
| |               +----------------+------+---------+-----------+---------------+
| +NodeIndexSeek  |              1 |    1 |       2 | person    | :Person(name) |
+-----------------+----------------+------+---------+-----------+---------------+

Total database accesses: 2

3.5.1.4. Use index with WHERE using equality

Indexes are also automatically used for equality comparisons of an indexed property in the WHERE clause. If you want Cypher to use specific indexes, you can enforce it using hints. See Section 3.6.4, “USING”.

Query. 

MATCH (person:Person)
WHERE person.name = 'Andres'
RETURN person

Query Plan. 

+-----------------+----------------+------+---------+-----------+---------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables | Other         |
+-----------------+----------------+------+---------+-----------+---------------+
| +ProduceResults |              1 |    1 |       0 | person    | person        |
| |               +----------------+------+---------+-----------+---------------+
| +NodeIndexSeek  |              1 |    1 |       2 | person    | :Person(name) |
+-----------------+----------------+------+---------+-----------+---------------+

Total database accesses: 2

3.5.1.5. Use index with WHERE using inequality

Indexes are also automatically used for inequality (range) comparisons of an indexed property in the WHERE clause. If you want Cypher to use specific indexes, you can enforce it using hints. See Section 3.6.4, “USING”.

Query. 

MATCH (person:Person)
WHERE person.name > 'B'
RETURN person

Query Plan. 

+-----------------------+----------------+------+---------+-----------+---------------------------------+
| Operator              | Estimated Rows | Rows | DB Hits | Variables | Other                           |
+-----------------------+----------------+------+---------+-----------+---------------------------------+
| +ProduceResults       |             10 |    1 |       0 | person    | person                          |
| |                     +----------------+------+---------+-----------+---------------------------------+
| +NodeIndexSeekByRange |             10 |    1 |       2 | person    | :Person(name) > {  AUTOSTRING0} |
+-----------------------+----------------+------+---------+-----------+---------------------------------+

Total database accesses: 2

3.5.1.6. Use index with IN

The IN predicate on person.name in the following query will use the Person(name) index, if it exists. If you want Cypher to use specific indexes, you can enforce it using hints. See Section 3.6.4, “USING”.

Query. 

MATCH (person:Person)
WHERE person.name IN ['Andres', 'Mark']
RETURN person

Query Plan. 

+-----------------+----------------+------+---------+-----------+---------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables | Other         |
+-----------------+----------------+------+---------+-----------+---------------+
| +ProduceResults |             24 |    2 |       0 | person    | person        |
| |               +----------------+------+---------+-----------+---------------+
| +NodeIndexSeek  |             24 |    2 |       4 | person    | :Person(name) |
+-----------------+----------------+------+---------+-----------+---------------+

Total database accesses: 4

3.5.1.7. Use index with STARTS WITH

The STARTS WITH predicate on person.name in the following query will use the Person(name) index, if it exists.

Query. 

MATCH (person:Person)
WHERE person.name STARTS WITH 'And'
RETURN person

Query Plan. 

+-----------------------+----------------+------+---------+-----------+-------------------------------------------+
| Operator              | Estimated Rows | Rows | DB Hits | Variables | Other                                     |
+-----------------------+----------------+------+---------+-----------+-------------------------------------------+
| +ProduceResults       |             26 |    1 |       0 | person    | person                                    |
| |                     +----------------+------+---------+-----------+-------------------------------------------+
| +NodeIndexSeekByRange |             26 |    1 |       2 | person    | :Person(name STARTS WITH {  AUTOSTRING0}) |
+-----------------------+----------------+------+---------+-----------+-------------------------------------------+

Total database accesses: 2

3.5.1.8. Use index when checking for the existence of a property

The has(p.name) predicate in the following query will use the Person(name) index, if it exists.

Query. 

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

Query Plan. 

+-----------------+----------------+------+---------+-----------+---------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables | Other         |
+-----------------+----------------+------+---------+-----------+---------------+
| +ProduceResults |              2 |    2 |       0 | p         | p             |
| |               +----------------+------+---------+-----------+---------------+
| +NodeIndexScan  |              2 |    2 |       3 | p         | :Person(name) |
+-----------------+----------------+------+---------+-----------+---------------+

Total database accesses: 3

3.5.2. Constraints

Neo4j helps enforce data integrity with the use of constraints. Constraints can be applied to either nodes or relationships. Unique node property constraints can be created, as well as node and relationship property existence constraints.

You can use unique property constraints to ensure that property values are unique for all nodes with a specific label. Unique constraints do not mean that all nodes have to have a unique value for the properties — nodes without the property are not subject to this rule.

You can use property existence constraints to ensure that a property exists for all nodes with a specific label or for all relationships with a specific type. All queries that try to create new nodes or relationships without the property, or queries that try to remove the mandatory property will now fail.

Property existence constraints are only available in the Neo4j Enterprise Edition. Note that databases with property existence constraints cannot be opened using Neo4j Community Edition.

You can have multiple constraints for a given label and you can also combine unique and property existence constraints on the same property.

Remember that adding constraints is an atomic operation that can take a while — all existing data has to be scanned before Neo4j can turn the constraint ``on''.

Note that adding a unique property constraint on a property will also add an index on that property, so you cannot add such an index separately. Cypher will use that index for lookups just like other indexes. If you drop a unique property constraint and still want an index on the property, you will have to create the index.

3.5.2.1. Unique node property constraints

Create uniqueness constraint

To create a constraint that makes sure that your database will never contain more than one node with a specific label and one property value, use the IS UNIQUE syntax.

Query. 

CREATE CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE

Result. 

+-------------------+
| No data returned. |
+-------------------+
Unique constraints added: 1

Drop uniqueness constraint

By using DROP CONSTRAINT, you remove a constraint from the database.

Query. 

DROP CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE

Result. 

+-------------------+
| No data returned. |
+-------------------+
Unique constraints removed: 1

Create a node that complies with unique property constraints

Create a Book node with an isbn that isn’t already in the database.

Query. 

CREATE (book:Book { isbn: '1449356265', title: 'Graph Databases' })

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 1
Properties set: 2
Labels added: 1

Create a node that breaks a unique property constraint

Create a Book node with an isbn that is already used in the database.

Query. 

CREATE (book:Book { isbn: '1449356265', title: 'Graph Databases' })

In this case the node isn’t created in the graph.

Error message. 

Node 0 already exists with label Book and property "isbn"=[1449356265]

Failure to create a unique property constraint due to conflicting nodes

Create a unique property constraint on the property isbn on nodes with the Book label when there are two nodes with the same isbn.

Query. 

CREATE CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE

In this case the constraint can’t be created because it is violated by existing data. We may choose to use Section 3.5.1, “Indexes” instead or remove the offending nodes and then re-apply the constraint.

Error message. 

Unable to create CONSTRAINT ON ( book:Book ) ASSERT book.isbn IS UNIQUE:
Multiple nodes with label `Book` have property `isbn` = '1449356265':
  node(0)
  node(1)

3.5.2.2. Node property existence constraints

Create node property existence constraint

To create a constraint that makes sure that all nodes with a certain label have a certain property, use the ASSERT exists(variable.propertyName) syntax.

Query. 

CREATE CONSTRAINT ON (book:Book) ASSERT exists(book.isbn)

Result. 

+-------------------+
| No data returned. |
+-------------------+
Property existence constraints added: 1

Drop node property existence constraint

By using DROP CONSTRAINT, you remove a constraint from the database.

Query. 

DROP CONSTRAINT ON (book:Book) ASSERT exists(book.isbn)

Result. 

+-------------------+
| No data returned. |
+-------------------+
Property existence constraints removed: 1

Create a node that complies with property existence constraints

Create a Book node with an existing isbn property.

Query. 

CREATE (book:Book { isbn: '1449356265', title: 'Graph Databases' })

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 1
Properties set: 2
Labels added: 1

Create a node that breaks a property existence constraint

Trying to create a Book node without an isbn property, given a property existence constraint on :Book(isbn).

Query. 

CREATE (book:Book { title: 'Graph Databases' })

In this case the node isn’t created in the graph.

Error message. 

Node 1 with label "Book" must have the property "isbn" due to a constraint

Removing an existence constrained node property

Trying to remove the isbn property from an existing node book, given a property existence constraint on :Book(isbn).

Query. 

MATCH (book:Book { title: 'Graph Databases' })
REMOVE book.isbn

In this case the property is not removed.

Error message. 

Node 0 with label "Book" must have the property "isbn" due to a constraint

Failure to create a node property existence constraint due to existing node

Create a constraint on the property isbn on nodes with the Book label when there already exists a node without an isbn.

Query. 

CREATE CONSTRAINT ON (book:Book) ASSERT exists(book.isbn)

In this case the constraint can’t be created because it is violated by existing data. We may choose to remove the offending nodes and then re-apply the constraint.

Error message. 

Unable to create CONSTRAINT ON ( book:Book ) ASSERT exists(book.isbn):
Node(0) with label `Book` has no value for property `isbn`

3.5.2.3. Relationship property existence constraints

Create relationship property existence constraint

To create a constraint that makes sure that all relationships with a certain type have a certain property, use the ASSERT exists(variable.propertyName) syntax.

Query. 

CREATE CONSTRAINT ON ()-[like:LIKED]-() ASSERT exists(like.day)

Result. 

+-------------------+
| No data returned. |
+-------------------+
Property existence constraints added: 1

Drop relationship property existence constraint

To remove a constraint from the database, use DROP CONSTRAINT.

Query. 

DROP CONSTRAINT ON ()-[like:LIKED]-() ASSERT exists(like.day)

Result. 

+-------------------+
| No data returned. |
+-------------------+
Property existence constraints removed: 1

Create a relationship that complies with property existence constraints

Create a LIKED relationship with an existing day property.

Query. 

CREATE (user:User)-[like:LIKED { day: 'yesterday' }]->(book:Book)

Result. 

+-------------------+
| No data returned. |
+-------------------+
Nodes created: 2
Relationships created: 1
Properties set: 1
Labels added: 2

Create a relationship that breaks a property existence constraint

Trying to create a LIKED relationship without a day property, given a property existence constraint :LIKED(day).

Query. 

CREATE (user:User)-[like:LIKED]->(book:Book)

In this case the relationship isn’t created in the graph.

Error message. 

Relationship 1 with type "LIKED" must have the property "day" due to a constraint

Removing an existence constrained relationship property

Trying to remove the day property from an existing relationship like of type LIKED, given a property existence constraint :LIKED(day).

Query. 

MATCH (user:User)-[like:LIKED]->(book:Book)
REMOVE like.day

In this case the property is not removed.

Error message. 

Relationship 0 with type "LIKED" must have the property "day" due to a constraint

Failure to create a relationship property existence constraint due to existing relationship

Create a constraint on the property day on relationships with the LIKED type when there already exists a relationship without a property named day.

Query. 

CREATE CONSTRAINT ON ()-[like:LIKED]-() ASSERT exists(like.day)

In this case the constraint can’t be created because it is violated by existing data. We may choose to remove the offending relationships and then re-apply the constraint.

Error message. 

Unable to create CONSTRAINT ON ()-[ liked:LIKED ]-() ASSERT exists(liked.day):
Relationship(0) with type `LIKED` has no value for property `day`

3.5.3. Statistics

When you issue a Cypher query, it gets compiled to an execution plan (see Section 3.7, “Execution Plans”) that can run and answer your question. To produce an efficient plan for your query, Neo4j needs information about your database, such as the schema — what indexes and constraints do exist? Neo4j will also use statistical information it keeps about your database to optimize the execution plan. With this information, Neo4j can decide which access pattern leads to the best performing plans.

The statistical information that Neo4j keeps is:

  1. The number of nodes with a certain label.
  2. Selectivity per index.
  3. The number of relationships by type.
  4. The number of relationships by type, ending or starting from a node with a specific label.

Neo4j keeps the statistics up to date in two different ways. For label counts for example, the number is updated whenever you set or remove a label from a node. For indexes, Neo4j needs to scan the full index to produce the selectivity number. Since this is potentially a very time-consuming operation, these numbers are collected in the background when enough data on the index has been changed.

3.5.3.1. Configuration options

Execution plans are cached and will not be replanned until the statistical information used to produce the plan has changed. The following configuration options allows you to control how sensitive replanning should be to updates of the database.

dbms.index_sampling.background_enabled

Controls whether indexes will automatically be re-sampled when they have been updated enough. The Cypher query planner depends on accurate statistics to create efficient plans, so it is important it is kept up to date as the database evolves.

If background sampling is turned off, make sure to trigger manual sampling when data has been updated.

dbms.index_sampling.update_percentage
Controls how large portion of the index has to have been updated before a new sampling run is triggered.
cypher.statistics_divergence_threshold
Controls how much the above statistical information is allowed to change before an execution plan is considered stale and has to be replanned. If the relative change in any of statistics is larger than this threshold, the plan will be thrown away and a new one will be created. A threshold of 0.0 means always replan, and a value of 1.0 means never replan.

3.5.3.2. Managing statistics from the shell

Usage:

schema sample -a
will sample all indexes.
schema sample -l Person -p name
will sample the index for label Person on property name (if existing).
schema sample -a -f
will force a sample of all indexes.
schema sample -f -l :Person -p name
will force sampling of a specific index.

3.6. Query Tuning

This section describes query tuning for the Cypher query language.

Neo4j 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. At least data should get 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. This also goes for what gets returned: avoid returning whole nodes and relationships — instead, pick the data you need and return only that. You should also make sure to set an upper limit on variable length patterns, so they don’t cover larger portions of the dataset than needed.

Each Cypher query gets optimized and transformed into an execution plan by the Cypher execution engine. To minimize the resources used for this, make sure to use parameters instead of literals when possible. This allows Cypher to re-use your queries instead of having to parse and build new execution plans.

To read more about the execution plan operators mentioned in this chapter, see Section 3.7, “Execution Plans”.

3.6.1. How are queries executed?

Each query is turned into an execution plan by something called the execution planner. The execution plan tells Neo4j which operations to perform when executing the query. Two different execution planning strategies are included in Neo4j:

Rule
This planner has rules that are used to produce execution plans. The planner considers available indexes, but does not use statistical information to guide the query compilation.
Cost
This planner uses the statistics service in Neo4j to assign cost to alternative plans and picks the cheapest one. While this should lead to superior execution plans in most cases, it is still under development.

By default, Neo4j 3.0 will use the cost planner for all queries. You can force it to use a specific planner by using the cypher.planner configuration setting (see Operations Manual → Configuration Settings), or by prepending your query with CYPHER planner=cost or CYPHER planner=rule.

You can see which planner was used by looking at the execution plan.

When Cypher is building execution plans, it looks at the schema to see if it can find indexes it can use. These index decisions are only valid until the schema changes, so adding or removing indexes leads to the execution plan cache being flushed.

3.6.2. Profiling a query

There are two options to choose from when you want to analyze a query by looking at its execution plan:

EXPLAIN
If you want to see the execution plan but not run the statement, prepend your Cypher statement with EXPLAIN. The statement will always return an empty result and make no changes to the database.
PROFILE
If you want to run the statement and see which operators are doing most of the work, use PROFILE. This will run your statement and keep track of how many rows pass through each operator, and how much each operator needs to interact with the storage layer to retrieve the necessary data. Please note that profiling your query uses more resources, so you should not profile unless you are actively working on a query.

See Section 3.7, “Execution Plans” for a detailed explanation of each of the operators contained in an execution plan.

Being explicit about what types and labels you expect relationships and nodes to have in your query helps Neo4j use the best possible statistical information, which leads to better execution plans. This means that when you know that a relationship can only be of a certain type, you should add that to the query. The same goes for labels, where declaring labels on both the start and end nodes of a relationship helps Neo4j find the best way to execute the statement.

3.6.3. Basic query tuning example

We’ll start with a basic example to help you get the hang of profiling queries. The following examples will use a movies data set.

Let’s start by importing the data:

LOAD CSV WITH HEADERS FROM "http://neo4j.com/docs/3.0.7/csv/query-tuning/movies.csv" AS line
MERGE (m:Movie { title:line.title })
ON CREATE SET m.released = toInt(line.released), m.tagline = line.tagline

LOAD CSV WITH HEADERS FROM 'http://neo4j.com/docs/3.0.7/csv/query-tuning/actors.csv' AS line
MATCH (m:Movie { title:line.title })
MERGE (p:Person { name:line.name })
ON CREATE SET p.born = toInt(line.born)
MERGE (p)-[:ACTED_IN { roles:split(line.roles,";")}]->(m)

LOAD CSV WITH HEADERS FROM 'http://neo4j.com/docs/3.0.7/csv/query-tuning/directors.csv' AS line
MATCH (m:Movie { title:line.title })
MERGE (p:Person { name:line.name })
ON CREATE SET p.born = toInt(line.born)
MERGE (p)-[:DIRECTED]->(m)

Let’s say we want to write a query to find Tom Hanks. The naive way of doing this would be to write the following:

MATCH (p { name:"Tom Hanks" })
RETURN p

This query will find the Tom Hanks node but as the number of nodes in the database increase it will become slower and slower. We can profile the query to find out why that is.

You can learn more about the options for profiling queries in Section 3.6.2, “Profiling a query” but in this case we’re going to prefix our query with PROFILE:

PROFILE
MATCH (p { name:"Tom Hanks" })
RETURN p
Compiler CYPHER 3.0

Planner COST

Runtime INTERPRETED

+-----------------+----------------+------+---------+-----------+---------------------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables | Other                     |
+-----------------+----------------+------+---------+-----------+---------------------------+
| +ProduceResults |             16 |    1 |       0 | p         | p                         |
| |               +----------------+------+---------+-----------+---------------------------+
| +Filter         |             16 |    1 |     163 | p         | p.name == {  AUTOSTRING0} |
| |               +----------------+------+---------+-----------+---------------------------+
| +AllNodesScan   |            163 |  163 |     164 | p         |                           |
+-----------------+----------------+------+---------+-----------+---------------------------+

Total database accesses: 327

The first thing to keep in mind when reading execution plans is that you need to read from the bottom up.

In that vein, starting from the last row, the first thing we notice is that the value in the Rows column seems high given there is only one node with the name property Tom Hanks in the database. If we look across to the Operator column we’ll see that AllNodesScan has been used which means that the query planner scanned through all the nodes in the database.

Moving up to the previous row we see the Filter operator which will check the name property on each of the nodes passed through by AllNodesScan.

This seems like an inefficient way of finding Tom Hanks given that we are looking at many nodes that aren’t even people and therefore aren’t what we’re looking for.

The solution to this problem is that whenever we’re looking for a node we should specify a label to help the query planner narrow down the search space. For this query we’d need to add a Person label.

MATCH (p:Person { name:"Tom Hanks" })
RETURN p

This query will be faster than the first one but as the number of people in our database increase we again notice that the query slows down.

Again we can profile the query to work out why:

PROFILE
MATCH (p:Person { name:"Tom Hanks" })
RETURN p
Compiler CYPHER 3.0

Planner COST

Runtime INTERPRETED

+------------------+----------------+------+---------+-----------+---------------------------+
| Operator         | Estimated Rows | Rows | DB Hits | Variables | Other                     |
+------------------+----------------+------+---------+-----------+---------------------------+
| +ProduceResults  |             13 |    1 |       0 | p         | p                         |
| |                +----------------+------+---------+-----------+---------------------------+
| +Filter          |             13 |    1 |     125 | p         | p.name == {  AUTOSTRING0} |
| |                +----------------+------+---------+-----------+---------------------------+
| +NodeByLabelScan |            125 |  125 |     126 | p         | :Person                   |
+------------------+----------------+------+---------+-----------+---------------------------+

Total database accesses: 251

This time the Rows value on the last row has reduced so we’re not scanning some nodes that we were before which is a good start. The NodeByLabelScan operator indicates that we achieved this by first doing a linear scan of all the Person nodes in the database.

Once we’ve done that we again scan through all those nodes using the Filter operator, comparing the name property of each one.

This might be acceptable in some cases but if we’re going to be looking up people by name frequently then we’ll see better performance if we create an index on the name property for the Person label:

CREATE INDEX ON :Person(name)

Now if we run the query again it will run more quickly:

MATCH (p:Person { name:"Tom Hanks" })
RETURN p

Let’s profile the query to see why that is:

PROFILE
MATCH (p:Person { name:"Tom Hanks" })
RETURN p
Compiler CYPHER 3.0

Planner COST

Runtime INTERPRETED

+-----------------+----------------+------+---------+-----------+---------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables | Other         |
+-----------------+----------------+------+---------+-----------+---------------+
| +ProduceResults |              1 |    1 |       0 | p         | p             |
| |               +----------------+------+---------+-----------+---------------+
| +NodeIndexSeek  |              1 |    1 |       2 | p         | :Person(name) |
+-----------------+----------------+------+---------+-----------+---------------+

Total database accesses: 2

Our execution plan is down to a single row and uses the Node Index Seek operator which does a schema index seek (see Section 3.5.1, “Indexes”) to find the appropriate node.

3.6.4. USING

The USING clause is used to influence the decisions of the planner when building an execution plan for a query.

Forcing planner behavior is an advanced feature, and should be used with caution by experienced developers and/or database administrators only, as it may cause queries to perform poorly.

3.6.4.1. Introduction

When executing a query, Neo4j needs to decide where in the query graph to start matching. This is done by looking at the MATCH clause and the WHERE conditions and using that information to find useful indexes, or other starting points.

However, the selected index might not always be the best choice. Sometimes multiple indexes are possible candidates, and the query planner picks the wrong one from a performance point of view. And in some circumstances (albeit rarely) it is better not to use an index at all.

You can force Neo4j to use a specific starting point through the USING clause. This is called giving a planner hint. There are three types of planner hints: index hints, scan hints, and join hints.

You cannot use planner hints if your query has a START clause.

The following graph is used for the examples below:

Figure 3.22. Graph
alt

Query. 

MATCH (liskov:Scientist { name:'Liskov' })-[:KNOWS]->(wing:Scientist)-[:RESEARCHED]->(cs:Science { name:'Computer Science' })<-[:RESEARCHED]-(conway:Scientist { name: 'Conway' })
RETURN 1 AS column

The following query will be used in some of the examples on this page. It has intentionally been constructed in such a way that the statistical information will be inaccurate for the particular subgraph that this query matches. For this reason, it can be improved by supplying planner hints.

Query plan. 

+------------------+----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| Operator         | Estimated Rows | Rows | DB Hits | Variables                                                         | Other                                               |
+------------------+----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| +ProduceResults  |              0 |    1 |       0 | column                                                            | column                                              |
| |                +----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| +Projection      |              0 |    1 |       0 | column -- anon[122], anon[41], anon[68], conway, cs, liskov, wing | {  AUTOINT3}                                        |
| |                +----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| +Filter          |              0 |    1 |       2 | anon[122], anon[41], anon[68], conway, cs, liskov, wing           | liskov:Scientist AND liskov.name == {  AUTOSTRING0} |
| |                +----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| +Expand(All)     |              0 |    1 |       3 | anon[41], liskov -- anon[122], anon[68], conway, cs, wing         | (wing)<-[:KNOWS]-(liskov)                           |
| |                +----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| +Filter          |              1 |    2 |       2 | anon[122], anon[68], conway, cs, wing                             | NOT(anon[122] == anon[68]) AND wing:Scientist       |
| |                +----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| +Expand(All)     |              1 |    3 |       4 | anon[68], wing -- anon[122], conway, cs                           | (cs)<-[:RESEARCHED]-(wing)                          |
| |                +----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| +Filter          |              0 |    1 |       4 | anon[122], conway, cs                                             | conway.name == {  AUTOSTRING2} AND conway:Scientist |
| |                +----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| +Expand(All)     |              3 |    3 |       4 | anon[122], conway -- cs                                           | (cs)<-[:RESEARCHED]-(conway)                        |
| |                +----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| +Filter          |              1 |    1 |       3 | cs                                                                | cs.name == {  AUTOSTRING1}                          |
| |                +----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+
| +NodeByLabelScan |              3 |    3 |       4 | cs                                                                | :Science                                            |
+------------------+----------------+------+---------+-------------------------------------------------------------------+-----------------------------------------------------+

Total database accesses: 26

Try this query live.  CREATE INDEX ON :Scientist(name) CREATE INDEX ON :Science(name) CREATE (liskov:Scientist {name:'Liskov', born: 1939})-[:KNOWS]->(wing:Scientist {name:'Wing', born: 1956})-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(conway:Scientist {name: 'Conway', born: 1938}), (liskov)-[:RESEARCHED]->(cs), (wing)-[:RESEARCHED]->(:Science {name: 'Engineering'}), (chemistry:Science {name: 'Chemistry'})<-[:RESEARCHED]-(:Scientist {name: 'Curie', born: 1867}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Arden'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Franklin'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Harrison'}) MATCH (liskov:Scientist {name:'Liskov'})-[:KNOWS]->(wing:Scientist)-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(conway:Scientist {name: 'Conway'}) RETURN 1 AS column

3.6.4.2. Index hints

Index hints are used to specify which index, if any, the planner should use as a starting point. This can be beneficial in cases where the index statistics are not accurate for the specific values that the query at hand is known to use, which would result in the planner picking a non-optimal index. To supply an index hint, use USING INDEX variable:Label(property) after the applicable MATCH clause.

It is possible to supply several index hints, but keep in mind that several starting points will require the use of a potentially expensive join later in the query plan.

Query using an index hint

The query above will not naturally pick an index to solve the plan. This is because the graph is very small, and label scans are faster for small databases. In general, however, query performance is ranked by the dbhit metric, and we see that using an index is slightly better for this query.

Query. 

MATCH (liskov:Scientist { name:'Liskov' })-[:KNOWS]->(wing:Scientist)-[:RESEARCHED]->(cs:Science { name:'Computer Science' })<-[:RESEARCHED]-(conway:Scientist { name: 'Conway' })
USING INDEX liskov:Scientist(name)
RETURN liskov.born AS column

Returns the year Barbara Liskov was born.

Query plan. 

+-----------------+----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables                                                         | Other                                                                              |
+-----------------+----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+
| +ProduceResults |              0 |    1 |       0 | column                                                            | column                                                                             |
| |               +----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+
| +Projection     |              0 |    1 |       1 | column -- anon[122], anon[41], anon[68], conway, cs, liskov, wing | liskov.born                                                                        |
| |               +----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+
| +Filter         |              0 |    1 |       4 | anon[122], anon[41], anon[68], conway, cs, liskov, wing           | NOT(anon[122] == anon[68]) AND conway:Scientist AND conway.name == {  AUTOSTRING2} |
| |               +----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+
| +Expand(All)    |              0 |    3 |       4 | anon[122], conway -- anon[41], anon[68], cs, liskov, wing         | (cs)<-[:RESEARCHED]-(conway)                                                       |
| |               +----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+
| +Filter         |              0 |    1 |       4 | anon[41], anon[68], cs, liskov, wing                              | cs:Science AND cs.name == {  AUTOSTRING1}                                          |
| |               +----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+
| +Expand(All)    |              0 |    2 |       3 | anon[68], cs -- anon[41], liskov, wing                            | (wing)-[:RESEARCHED]->(cs)                                                         |
| |               +----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+
| +Filter         |              0 |    1 |       1 | anon[41], liskov, wing                                            | wing:Scientist                                                                     |
| |               +----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+
| +Expand(All)    |              0 |    1 |       2 | anon[41], wing -- liskov                                          | (liskov)-[:KNOWS]->(wing)                                                          |
| |               +----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+
| +NodeIndexSeek  |              1 |    1 |       2 | liskov                                                            | :Scientist(name)                                                                   |
+-----------------+----------------+------+---------+-------------------------------------------------------------------+------------------------------------------------------------------------------------+

Total database accesses: 21

Try this query live.  CREATE INDEX ON :Scientist(name) CREATE INDEX ON :Science(name) CREATE (liskov:Scientist {name:'Liskov', born: 1939})-[:KNOWS]->(wing:Scientist {name:'Wing', born: 1956})-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(conway:Scientist {name: 'Conway', born: 1938}), (liskov)-[:RESEARCHED]->(cs), (wing)-[:RESEARCHED]->(:Science {name: 'Engineering'}), (chemistry:Science {name: 'Chemistry'})<-[:RESEARCHED]-(:Scientist {name: 'Curie', born: 1867}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Arden'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Franklin'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Harrison'}) MATCH (liskov:Scientist {name:'Liskov'})-[:KNOWS]->(wing:Scientist)-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(conway:Scientist {name: 'Conway'}) USING INDEX liskov:Scientist(name) RETURN liskov.born AS column

Query using multiple index hints

Supplying one index hint changed the starting point of the query, but the plan is still linear, meaning it only has one starting point. If we give the planner yet another index hint, we force it to use two starting points, one at each end of the match. It will then join these two branches using a join operator.

Query. 

MATCH (liskov:Scientist { name:'Liskov' })-[:KNOWS]->(wing:Scientist)-[:RESEARCHED]->(cs:Science { name:'Computer Science' })<-[:RESEARCHED]-(conway:Scientist { name: 'Conway' })
USING INDEX liskov:Scientist(name)
USING INDEX conway:Scientist(name)
RETURN liskov.born AS column

Returns the year Barbara Liskov was born, using a slightly better plan.

Query plan. 

+------------------+----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| Operator         | Estimated Rows | Rows | DB Hits | Variables                                                         | Other                                     |
+------------------+----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +ProduceResults  |              0 |    1 |       0 | column                                                            | column                                    |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +Projection      |              0 |    1 |       1 | column -- anon[122], anon[41], anon[68], conway, cs, liskov, wing | liskov.born                               |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +Filter          |              0 |    1 |       0 | anon[122], anon[41], anon[68], conway, cs, liskov, wing           | NOT(anon[122] == anon[68])                |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +NodeHashJoin    |              0 |    1 |       0 | anon[41], anon[68], liskov, wing -- anon[122], conway, cs         | cs                                        |
| |\               +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| | +Filter        |              1 |    1 |       1 | anon[122], conway, cs                                             | cs.name == {  AUTOSTRING1}                |
| | |              +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| | +Expand(All)   |              1 |    1 |       2 | anon[122], cs -- conway                                           | (conway)-[:RESEARCHED]->(cs)              |
| | |              +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| | +NodeIndexSeek |              1 |    1 |       2 | conway                                                            | :Scientist(name)                          |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +Filter          |              0 |    1 |       4 | anon[41], anon[68], cs, liskov, wing                              | cs:Science AND cs.name == {  AUTOSTRING1} |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +Expand(All)     |              0 |    2 |       3 | anon[68], cs -- anon[41], liskov, wing                            | (wing)-[:RESEARCHED]->(cs)                |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +Filter          |              0 |    1 |       1 | anon[41], liskov, wing                                            | wing:Scientist                            |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +Expand(All)     |              0 |    1 |       2 | anon[41], wing -- liskov                                          | (liskov)-[:KNOWS]->(wing)                 |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +NodeIndexSeek   |              1 |    1 |       2 | liskov                                                            | :Scientist(name)                          |
+------------------+----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+

Total database accesses: 18

Try this query live.  CREATE INDEX ON :Scientist(name) CREATE INDEX ON :Science(name) CREATE (liskov:Scientist {name:'Liskov', born: 1939})-[:KNOWS]->(wing:Scientist {name:'Wing', born: 1956})-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(conway:Scientist {name: 'Conway', born: 1938}), (liskov)-[:RESEARCHED]->(cs), (wing)-[:RESEARCHED]->(:Science {name: 'Engineering'}), (chemistry:Science {name: 'Chemistry'})<-[:RESEARCHED]-(:Scientist {name: 'Curie', born: 1867}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Arden'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Franklin'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Harrison'}) MATCH (liskov:Scientist {name:'Liskov'})-[:KNOWS]->(wing:Scientist)-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(conway:Scientist {name: 'Conway'}) USING INDEX liskov:Scientist(name) USING INDEX conway:Scientist(name) RETURN liskov.born AS column

3.6.4.3. Scan hints

If your query matches large parts of an index, it might be faster to scan the label and filter out nodes that do not match. To do this, you can use USING SCAN variable:Label after the applicable MATCH clause. This will force Cypher to not use an index that could have been used, and instead do a label scan.

Hinting a label scan

If the best performance is to be had by scanning all nodes in a label and then filtering on that set, use USING SCAN.

Query. 

MATCH (s:Scientist)
USING SCAN s:Scientist
WHERE s.born < 1939
RETURN s.born AS column

Returns all scientists born before 1939.

Query plan. 

+------------------+----------------+------+---------+-------------+-------------------------------------------------------------------+
| Operator         | Estimated Rows | Rows | DB Hits | Variables   | Other                                                             |
+------------------+----------------+------+---------+-------------+-------------------------------------------------------------------+
| +ProduceResults  |              0 |    2 |       0 | column      | column                                                            |
| |                +----------------+------+---------+-------------+-------------------------------------------------------------------+
| +Projection      |              0 |    2 |       2 | column -- s | s.born                                                            |
| |                +----------------+------+---------+-------------+-------------------------------------------------------------------+
| +Filter          |              0 |    2 |       7 | s           | AndedPropertyComparablePredicates(s,s.born,s.born < {  AUTOINT0}) |
| |                +----------------+------+---------+-------------+-------------------------------------------------------------------+
| +NodeByLabelScan |              7 |    7 |       8 | s           | :Scientist                                                        |
+------------------+----------------+------+---------+-------------+-------------------------------------------------------------------+

Total database accesses: 17

Try this query live.  CREATE INDEX ON :Scientist(name) CREATE INDEX ON :Science(name) CREATE (liskov:Scientist {name:'Liskov', born: 1939})-[:KNOWS]->(wing:Scientist {name:'Wing', born: 1956})-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(conway:Scientist {name: 'Conway', born: 1938}), (liskov)-[:RESEARCHED]->(cs), (wing)-[:RESEARCHED]->(:Science {name: 'Engineering'}), (chemistry:Science {name: 'Chemistry'})<-[:RESEARCHED]-(:Scientist {name: 'Curie', born: 1867}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Arden'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Franklin'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Harrison'}) MATCH (s:Scientist) USING SCAN s:Scientist WHERE s.born < 1939 RETURN s.born AS column

3.6.4.4. Join hints

Join hints are the most advanced type of hints, and are not used to find starting points for the query execution plan, but to enforce that joins are made at specified points. This implies that there has to be more than one starting point (leaf) in the plan, in order for the query to be able to join the two branches ascending from these leaves. Due to this nature, joins, and subsequently join hints, will force the planner to look for additional starting points, and in the case where there are no more good ones, potentially pick a very bad starting point. This will negatively affect query performance. In other cases, the hint might force the planner to pick a seemingly bad starting point, which in reality proves to be a very good one.

Hinting a join on a single node

In the example above using multiple index hints, we saw that the planner chose to do a join on the cs node. This means that the relationship between wing and cs was traversed in the outgoing direction, which is better statistically because the pattern ()-[:RESEARCHED]→(:Science) is more common than the pattern (:Scientist)-[:RESEARCHED]→(). However, in the actual graph, the cs node only has two such relationships, so expanding from it will be beneficial to expanding from the wing node. We can force the join to happen on wing instead with a join hint.

Query. 

MATCH (liskov:Scientist { name:'Liskov' })-[:KNOWS]->(wing:Scientist)-[:RESEARCHED]->(cs:Science { name:'Computer Science' })<-[:RESEARCHED]-(conway:Scientist { name: 'Conway' })
USING INDEX liskov:Scientist(name)
USING INDEX conway:Scientist(name)
USING JOIN ON wing
RETURN wing.born AS column

Returns the birth date of Jeanette Wing, using a slightly better plan.

Query plan. 

+------------------+----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| Operator         | Estimated Rows | Rows | DB Hits | Variables                                                         | Other                                     |
+------------------+----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +ProduceResults  |              0 |    1 |       0 | column                                                            | column                                    |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +Projection      |              0 |    1 |       1 | column -- anon[122], anon[41], anon[68], conway, cs, liskov, wing | wing.born                                 |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +NodeHashJoin    |              0 |    1 |       0 | anon[41], liskov -- anon[122], anon[68], conway, cs, wing         | wing                                      |
| |\               +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| | +Filter        |              1 |    2 |       0 | anon[122], anon[68], conway, cs, wing                             | NOT(anon[122] == anon[68])                |
| | |              +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| | +Expand(All)   |              1 |    3 |       4 | anon[68], wing -- anon[122], conway, cs                           | (cs)<-[:RESEARCHED]-(wing)                |
| | |              +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| | +Filter        |              0 |    1 |       2 | anon[122], conway, cs                                             | cs:Science AND cs.name == {  AUTOSTRING1} |
| | |              +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| | +Expand(All)   |              1 |    1 |       2 | anon[122], cs -- conway                                           | (conway)-[:RESEARCHED]->(cs)              |
| | |              +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| | +NodeIndexSeek |              1 |    1 |       2 | conway                                                            | :Scientist(name)                          |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +Filter          |              0 |    1 |       1 | anon[41], liskov, wing                                            | wing:Scientist                            |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +Expand(All)     |              0 |    1 |       2 | anon[41], wing -- liskov                                          | (liskov)-[:KNOWS]->(wing)                 |
| |                +----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+
| +NodeIndexSeek   |              1 |    1 |       2 | liskov                                                            | :Scientist(name)                          |
+------------------+----------------+------+---------+-------------------------------------------------------------------+-------------------------------------------+

Total database accesses: 16

Try this query live.  CREATE INDEX ON :Scientist(name) CREATE INDEX ON :Science(name) CREATE (liskov:Scientist {name:'Liskov', born: 1939})-[:KNOWS]->(wing:Scientist {name:'Wing', born: 1956})-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(conway:Scientist {name: 'Conway', born: 1938}), (liskov)-[:RESEARCHED]->(cs), (wing)-[:RESEARCHED]->(:Science {name: 'Engineering'}), (chemistry:Science {name: 'Chemistry'})<-[:RESEARCHED]-(:Scientist {name: 'Curie', born: 1867}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Arden'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Franklin'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Harrison'}) MATCH (liskov:Scientist {name:'Liskov'})-[:KNOWS]->(wing:Scientist)-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(conway:Scientist {name: 'Conway'}) USING INDEX liskov:Scientist(name) USING INDEX conway:Scientist(name) USING JOIN ON wing RETURN wing.born AS column

Hinting a join on multiple nodes

The query planner can be made to produce a join between several specific points. This requires the query to expand from the same node from several directions.

Query. 

MATCH (liskov:Scientist { name:'Liskov' })-[:KNOWS]->(wing:Scientist { name:'Wing' })-[:RESEARCHED]->(cs:Science { name:'Computer Science' })<-[:RESEARCHED]-(liskov)
USING INDEX liskov:Scientist(name)
USING JOIN ON liskov, cs
RETURN wing.born AS column

Returns the birth date of Jeanette Wing.

Query plan. 

+------------------+----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| Operator         | Estimated Rows | Rows | DB Hits | Variables                                                 | Other                                           |
+------------------+----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| +ProduceResults  |              0 |    1 |       0 | column                                                    | column                                          |
| |                +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| +Projection      |              0 |    1 |       1 | column -- anon[136], anon[41], anon[82], cs, liskov, wing | wing.born                                       |
| |                +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| +Filter          |              0 |    1 |       0 | anon[136], anon[41], anon[82], cs, liskov, wing           | NOT(anon[136] == anon[82])                      |
| |                +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| +NodeHashJoin    |              0 |    1 |       0 | anon[41], anon[82], wing -- anon[136], cs, liskov         | liskov, cs                                      |
| |\               +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| | +Filter        |              1 |    1 |       2 | anon[136], cs, liskov                                     | cs:Science AND cs.name == {  AUTOSTRING2}       |
| | |              +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| | +Expand(All)   |              1 |    1 |       2 | anon[136], cs -- liskov                                   | (liskov)-[:RESEARCHED]->(cs)                    |
| | |              +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| | +NodeIndexSeek |              1 |    1 |       2 | liskov                                                    | :Scientist(name)                                |
| |                +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| +Filter          |              0 |    1 |       4 | anon[41], anon[82], cs, liskov, wing                      | cs:Science AND cs.name == {  AUTOSTRING2}       |
| |                +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| +Expand(All)     |              0 |    2 |       3 | anon[82], cs -- anon[41], liskov, wing                    | (wing)-[:RESEARCHED]->(cs)                      |
| |                +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| +Filter          |              0 |    1 |       2 | anon[41], liskov, wing                                    | wing.name == {  AUTOSTRING1} AND wing:Scientist |
| |                +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| +Expand(All)     |              0 |    1 |       2 | anon[41], wing -- liskov                                  | (liskov)-[:KNOWS]->(wing)                       |
| |                +----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+
| +NodeIndexSeek   |              1 |    1 |       2 | liskov                                                    | :Scientist(name)                                |
+------------------+----------------+------+---------+-----------------------------------------------------------+-------------------------------------------------+

Total database accesses: 20

Try this query live.  CREATE INDEX ON :Scientist(name) CREATE INDEX ON :Science(name) CREATE (liskov:Scientist {name:'Liskov', born: 1939})-[:KNOWS]->(wing:Scientist {name:'Wing', born: 1956})-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(conway:Scientist {name: 'Conway', born: 1938}), (liskov)-[:RESEARCHED]->(cs), (wing)-[:RESEARCHED]->(:Science {name: 'Engineering'}), (chemistry:Science {name: 'Chemistry'})<-[:RESEARCHED]-(:Scientist {name: 'Curie', born: 1867}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Arden'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Franklin'}), (chemistry)<-[:RESEARCHED]-(:Scientist {name: 'Harrison'}) MATCH (liskov:Scientist {name:'Liskov'})-[:KNOWS]->(wing:Scientist {name:'Wing'})-[:RESEARCHED]->(cs:Science {name:'Computer Science'})<-[:RESEARCHED]-(liskov) USING INDEX liskov:Scientist(name) USING JOIN ON liskov, cs RETURN wing.born AS column

3.7. Execution Plans

This section describes operators used as part of an execution plan to execute a query in the Cypher query language.

Neo4j breaks down the work of executing a query into small pieces called operators. Each operator is responsible for a small part of the overall query. The operators are connected together in a pattern called a execution plan.

Each operator is annotated with statistics.

Rows
The number of rows that the operator produced. Only available if the query was profiled.
EstimatedRows
If Neo4j used the cost-based compiler you will see the estimated number of rows that will be produced by the operator. The compiler uses this estimate to choose a suitable execution plan.
DbHits
Each operator will ask the Neo4j storage engine to do work such as retrieving or updating data. A database hit is an abstract unit of this storage engine work.

See Section 3.6.2, “Profiling a query” for how to view the execution plan for your query.

For a deeper understanding of how each operator works, see the relevant section. Operators are grouped into high-level categories. Please remember that the statistics of the actual database where the queries run on will decide the plan used. There is no guarantee that a specific query will always be solved with the same plan.

3.7.1. Starting point operators

These operators find parts of the graph from which to start.

3.7.1.1. All Nodes Scan

Reads all nodes from the node store. The variable that will contain the nodes is seen in the arguments. If your query is using this operator, you are very likely to see performance problems on any non-trivial database.

Query. 

MATCH (n)
RETURN n

Query Plan. 

+-----------------+----------------+------+---------+-----------+-------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables | Other |
+-----------------+----------------+------+---------+-----------+-------+
| +ProduceResults |             35 |   35 |       0 | n         | n     |
| |               +----------------+------+---------+-----------+-------+
| +AllNodesScan   |             35 |   35 |      36 | n         |       |
+-----------------+----------------+------+---------+-----------+-------+

Total database accesses: 36

3.7.1.2. Directed Relationship By Id Seek

Reads one or more relationships by id from the relationship store. Produces both the relationship and the nodes on either side.

Query. 

MATCH (n1)-[r]->()
WHERE id(r)= 0
RETURN r, n1

Query Plan. 

+-----------------------------------+----------------+------+---------+-----------------+--------------------------------------------+
| Operator                          | Estimated Rows | Rows | DB Hits | Variables       | Other                                      |
+-----------------------------------+----------------+------+---------+-----------------+--------------------------------------------+
| +ProduceResults                   |              1 |    1 |       0 | n1, r           | r, n1                                      |
| |                                 +----------------+------+---------+-----------------+--------------------------------------------+
| +DirectedRelationshipByIdSeekPipe |              1 |    1 |       1 | anon[17], n1, r | EntityByIdRhs(SingleSeekArg({  AUTOINT0})) |
+-----------------------------------+----------------+------+---------+-----------------+--------------------------------------------+

Total database accesses: 1

3.7.1.3. Node by Id seek

Reads one or more nodes by id from the node store.

Query. 

MATCH (n)
WHERE id(n)= 0
RETURN n

Query Plan. 

+-----------------+----------------+------+---------+-----------+-------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables | Other |
+-----------------+----------------+------+---------+-----------+-------+
| +ProduceResults |              1 |    1 |       0 | n         | n     |
| |               +----------------+------+---------+-----------+-------+
| +NodeByIdSeek   |              1 |    1 |       1 | n         |       |
+-----------------+----------------+------+---------+-----------+-------+

Total database accesses: 1

3.7.1.4. Node by label scan

Using the label index, fetches all nodes with a specific label on them from the node label index.

Query. 

MATCH (person:Person)
RETURN person

Query Plan. 

+------------------+----------------+------+---------+-----------+---------+
| Operator         | Estimated Rows | Rows | DB Hits | Variables | Other   |
+------------------+----------------+------+---------+-----------+---------+
| +ProduceResults  |             14 |   14 |       0 | person    | person  |
| |                +----------------+------+---------+-----------+---------+
| +NodeByLabelScan |             14 |   14 |      15 | person    | :Person |
+------------------+----------------+------+---------+-----------+---------+

Total database accesses: 15

3.7.1.5. Node index seek

Finds nodes using an index seek. The node variable and the index used is shown in the arguments of the operator. If the index is a unique index, the operator is called NodeUniqueIndexSeek instead.

Query. 

MATCH (location:Location { name: "Malmo" })
RETURN location

Query Plan. 

+-----------------+----------------+------+---------+-----------+-----------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables | Other           |
+-----------------+----------------+------+---------+-----------+-----------------+
| +ProduceResults |              1 |    1 |       0 | location  | location        |
| |               +----------------+------+---------+-----------+-----------------+
| +NodeIndexSeek  |              1 |    1 |       2 | location  | :Location(name) |
+-----------------+----------------+------+---------+-----------+-----------------+

Total database accesses: 2

3.7.1.6. Node index range seek

Finds nodes using an index seek where the value of the property matches a given prefix string. This operator can be used for STARTS WITH and comparators such as <, >, and >=

Query. 

MATCH (l:Location)
WHERE l.name STARTS WITH 'Lon'
RETURN l

Query Plan. 

+-----------------------+----------------+------+---------+-----------+---------------------------------------------+
| Operator              | Estimated Rows | Rows | DB Hits | Variables | Other                                       |
+-----------------------+----------------+------+---------+-----------+---------------------------------------------+
| +ProduceResults       |             26 |    1 |       0 | l         | l                                           |
| |                     +----------------+------+---------+-----------+---------------------------------------------+
| +NodeIndexSeekByRange |             26 |    1 |       2 | l         | :Location(name STARTS WITH {  AUTOSTRING0}) |
+-----------------------+----------------+------+---------+-----------+---------------------------------------------+

Total database accesses: 2

3.7.1.7. Node index contains scan

An index contains scan goes through all values stored in an index, and searches for entries containing a specific string. This is slower than an index seek, since all entries need to be examined, but still faster than the indirection needed by a label scan and then a property store filter.

Query. 

MATCH (l:Location)
WHERE l.name CONTAINS 'al'
RETURN l

Query Plan. 

+------------------------+----------------+------+---------+-----------+----------------------------------+
| Operator               | Estimated Rows | Rows | DB Hits | Variables | Other                            |
+------------------------+----------------+------+---------+-----------+----------------------------------+
| +ProduceResults        |             10 |    2 |       0 | l         | l                                |
| |                      +----------------+------+---------+-----------+----------------------------------+
| +NodeIndexContainsScan |             10 |    2 |       3 | l         | :Location(name); {  AUTOSTRING0} |
+------------------------+----------------+------+---------+-----------+----------------------------------+

Total database accesses: 3

3.7.1.8. Node index scan

An index scan goes through all values stored in an index, and can be used to find all nodes with a particular label having a specified property (e.g. exists(n.prop)).

Query. 

MATCH (l:Location)
WHERE exists(l.name)
RETURN l

Query Plan. 

+-----------------+----------------+------+---------+-----------+-----------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables | Other           |
+-----------------+----------------+------+---------+-----------+-----------------+
| +ProduceResults |             10 |   10 |       0 | l         | l               |
| |               +----------------+------+---------+-----------+-----------------+
| +NodeIndexScan  |             10 |   10 |      11 | l         | :Location(name) |
+-----------------+----------------+------+---------+-----------+-----------------+

Total database accesses: 11

3.7.1.9. Undirected Relationship By Id Seek

Reads one or more relationships by id from the relationship store. For each relationship, two rows are produced with start and end nodes arranged differently.

Query. 

MATCH (n1)-[r]-()
WHERE id(r)= 1
RETURN r, n1

Query Plan. 

+---------------------------------+----------------+------+---------+-----------------+-------+
| Operator                        | Estimated Rows | Rows | DB Hits | Variables       | Other |
+---------------------------------+----------------+------+---------+-----------------+-------+
| +ProduceResults                 |              1 |    2 |       0 | n1, r           | r, n1 |
| |                               +----------------+------+---------+-----------------+-------+
| +UndirectedRelationshipByIdSeek |              1 |    2 |       1 | anon[16], n1, r |       |
+---------------------------------+----------------+------+---------+-----------------+-------+

Total database accesses: 1

3.7.2. Expand operators

These operators explore the graph by expanding graph patterns.

3.7.2.1. Expand All

Given a start node, expand-all will follow relationships coming in or out, depending on the pattern relationship. Can also handle variable length pattern relationships.

Query. 

MATCH (p:Person { name: "me" })-[:FRIENDS_WITH]->(fof)
RETURN fof

Query Plan. 

+-----------------+----------------+------+---------+--------------------+----------------------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables          | Other                      |
+-----------------+----------------+------+---------+--------------------+----------------------------+
| +ProduceResults |              0 |    1 |       0 | fof                | fof                        |
| |               +----------------+------+---------+--------------------+----------------------------+
| +Expand(All)    |              0 |    1 |       2 | anon[30], fof -- p | (p)-[:FRIENDS_WITH]->(fof) |
| |               +----------------+------+---------+--------------------+----------------------------+
| +NodeIndexSeek  |              1 |    1 |       2 | p                  | :Person(name)              |
+-----------------+----------------+------+---------+--------------------+----------------------------+

Total database accesses: 4

3.7.2.2. Expand Into

When both the start and end node have already been found, expand-into is used to find all connecting relationships between the two nodes.

Query. 

MATCH (p:Person { name: "me" })-[:FRIENDS_WITH]->(fof)-->(p)
RETURN fof

Query Plan. 

+-----------------+----------------+------+---------+------------------------------+----------------------------+
| Operator        | Estimated Rows | Rows | DB Hits | Variables                    | Other                      |
+-----------------+----------------+------+---------+------------------------------+----------------------------+
| +ProduceResults |              0 |    0 |       0 | fof                          | fof                        |
| |               +----------------+------+---------+------------------------------+----------------------------+
| +Filter         |              0 |    0 |       0 | anon[30], anon[53], fof, p   | NOT(anon[30] == anon[53])  |
| |               +----------------+------+---------+------------------------------+----------------------------+
| +Expand(Into)   |              0 |    0 |       0 | anon[30] -- anon[53], fof, p | (p)-[:FRIENDS_WITH]->(fof) |
| |               +----------------+------+---------+------------------------------+----------------------------+
| +Expand(All)    |              0 |    0 |       1 | anon[53], fof -- p           | (p)<--(fof)                |
| |               +----------------+------+---------+------------------------------+----------------------------+
| +NodeIndexSeek  |              1 |    1 |       2 | p                            | :Person(name)              |
+-----------------+----------------+------+---------+------------------------------+----------------------------+

Total database accesses: 3

3.7.2.3. Optional Expand All

Optional expand traverses relationships from a given node, and makes sure that predicates are evaluated before producing rows.

If no matching relationships are found, a single row with NULL for the relationship and end node variable is produced.

Query. 

MATCH (p:Person)
OPTIONAL MATCH (p)-[works_in:WORKS_IN]->(l)
WHERE works_in.duration > 180
RETURN p, l

Query Plan. 

+----------------------+----------------+------+---------+------------------+------------------------------+
| Operator             | Estimated Rows | Rows | DB Hits | Variables        | Other                        |
+----------------------+----------------+------+---------+------------------+------------------------------+
| +ProduceResults      |             14 |   15 |       0 | l, p             | p, l                         |
| |                    +----------------+------+---------+------------------+------------------------------+
| +OptionalExpand(All) |             14 |   15 |      44 | l, works_in -- p | (p)-[works_in:WORKS_IN]->(l) |
| |                    +----------------+------+---------+------------------+------------------------------+
| +NodeByLabelScan     |             14 |   14 |      15 | p                | :Person                      |
+----------------------+----------------+------+---------+------------------+------------------------------+

Total database accesses: 59

3.7.3. Combining operators

The combining operators are used to piece together other operators.

The following graph is used for the examples below:

Figure 3.23. Graph
alt

3.7.3.1. Apply

Apply works by performing a nested loop. Every row being produced on the left-hand side of the Apply operator will be fed to the leaf operator on the right-hand side, and then Apply will yield the combined results. Apply, being a nested loop, can be seen as a warning that a better plan was not found.

Query. 

MATCH (p:Person)-[:FRIENDS_WITH]->(f)
WITH p, count(f) AS fs
WHERE fs > 2
OPTIONAL MATCH (p)-[:WORKS_IN]->(city)
RETURN city.name

Finds all the people with more than two friends and returns the city they work in.

Query plan. 

+----------------------+----------------+----------------------------------------------+--------------------------+
| Operator             | Estimated Rows | Variables                                    | Other                    |
+----------------------+----------------+----------------------------------------------+--------------------------+
| +ProduceResults      |              1 | city.name                                    | city.name                |
| |                    +----------------+----------------------------------------------+--------------------------+
| +Projection          |              1 | city.name -- anon[70], anon[93], city, fs, p | city.name                |
| |                    +----------------+----------------------------------------------+--------------------------+
| +OptionalExpand(All) |              1 | anon[93], city -- anon[70], fs, p            | (p)-[:WORKS_IN]->(city)  |
| |                    +----------------+----------------------------------------------+--------------------------+
| +Filter              |              1 | anon[70], fs, p                              | anon[70]                 |
| |                    +----------------+----------------------------------------------+--------------------------+
| +Projection          |              1 | anon[70] -- fs, p                            | p; fs; fs > {  AUTOINT0} |
| |                    +----------------+----------------------------------------------+--------------------------+
| +EagerAggregation    |              1 | fs -- p                                      | p                        |
| |                    +----------------+----------------------------------------------+--------------------------+
| +Expand(All)         |              2 | anon[17], f -- p                             | (p)-[:FRIENDS_WITH]->(f) |
| |                    +----------------+----------------------------------------------+--------------------------+
| +NodeByLabelScan     |             14 | p                                            | :Person                  |
+----------------------+----------------+----------------------------------------------+--------------------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MATCH (p:Person)-[:FRIENDS_WITH]->(f) WITH p, count(f) as fs WHERE fs > 2 OPTIONAL MATCH (p)-[:WORKS_IN]->(city) RETURN city.name

3.7.3.2. SemiApply

Tests for the existence of a pattern predicate. SemiApply takes a row from its child operator and feeds it to the leaf operator on the right-hand side. If the right-hand side operator tree yields at least one row, the row from the left-hand side is yielded by the SemiApply operator. This makes SemiApply a filtering operator, used mostly for pattern predicates in queries.

Query. 

MATCH (p:Person)
WHERE (p)-[:FRIENDS_WITH]->()
RETURN p.name

Finds all the people who have friends.

Query plan. 

+------------------+----------------+-------------------------+-------------------------+
| Operator         | Estimated Rows | Variables               | Other                   |
+------------------+----------------+-------------------------+-------------------------+
| +ProduceResults  |             11 | p.name                  | p.name                  |
| |                +----------------+-------------------------+-------------------------+
| +Projection      |             11 | p.name -- p             | p.name                  |
| |                +----------------+-------------------------+-------------------------+
| +SemiApply       |             11 | p                       |                         |
| |\               +----------------+-------------------------+-------------------------+
| | +Expand(All)   |              2 | anon[27], anon[45] -- p | (p)-[:FRIENDS_WITH]->() |
| | |              +----------------+-------------------------+-------------------------+
| | +Argument      |             14 | p                       |                         |
| |                +----------------+-------------------------+-------------------------+
| +NodeByLabelScan |             14 | p                       | :Person                 |
+------------------+----------------+-------------------------+-------------------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MATCH (p:Person) WHERE (p)-[:FRIENDS_WITH]->() RETURN p.name

3.7.3.3. AntiSemiApply

Tests for the existence of a pattern predicate. SemiApply takes a row from its child operator and feeds it to the leaf operator on the right-hand side. If the right-hand side operator tree yields at least one row, the row from the left-hand side is yielded by the SemiApply operator. This makes SemiApply a filtering operator, used mostly for pattern predicates in queries.

Query. 

MATCH (me:Person { name: "me" }),(other:Person)
WHERE NOT (me)-[:FRIENDS_WITH]->(other)
RETURN other.name

Finds the names of all the people who are not my friends.

Query plan. 

+--------------------+----------------+-------------------------+-------------------------------+
| Operator           | Estimated Rows | Variables               | Other                         |
+--------------------+----------------+-------------------------+-------------------------------+
| +ProduceResults    |              4 | other.name              | other.name                    |
| |                  +----------------+-------------------------+-------------------------------+
| +Projection        |              4 | other.name -- me, other | other.name                    |
| |                  +----------------+-------------------------+-------------------------------+
| +AntiSemiApply     |              4 | me, other               |                               |
| |\                 +----------------+-------------------------+-------------------------------+
| | +Expand(Into)    |              0 | anon[62] -- me, other   | (me)-[:FRIENDS_WITH]->(other) |
| | |                +----------------+-------------------------+-------------------------------+
| | +Argument        |             14 | me, other               |                               |
| |                  +----------------+-------------------------+-------------------------------+
| +CartesianProduct  |             14 | me -- other             |                               |
| |\                 +----------------+-------------------------+-------------------------------+
| | +NodeByLabelScan |             14 | other                   | :Person                       |
| |                  +----------------+-------------------------+-------------------------------+
| +NodeIndexSeek     |              1 | me                      | :Person(name)                 |
+--------------------+----------------+-------------------------+-------------------------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MATCH (me:Person {name: "me"}), (other:Person) WHERE NOT (me)-[:FRIENDS_WITH]->(other) RETURN other.name

3.7.3.4. LetSemiApply

Tests for the existence of a pattern predicate. When a query contains multiple pattern predicates LetSemiApply will be used to evaluate the first of these. It will record the result of evaluating the predicate but will leave any filtering to a another operator.

Query. 

MATCH (other:Person)
WHERE (other)-[:FRIENDS_WITH]->() OR (other)-[:WORKS_IN]->()
RETURN other.name

Finds the names of all the people who have a friend or who work somewhere. The LetSemiApply operator will be used to check for the existence of the FRIENDS_WITH relationship from each person.

Query plan. 

+--------------------+----------------+-------------------------------+-----------------------------+
| Operator           | Estimated Rows | Variables                     | Other                       |
+--------------------+----------------+-------------------------------+-----------------------------+
| +ProduceResults    |             13 | other.name                    | other.name                  |
| |                  +----------------+-------------------------------+-----------------------------+
| +Projection        |             13 | other.name -- anon[27], other | other.name                  |
| |                  +----------------+-------------------------------+-----------------------------+
| +SelectOrSemiApply |             13 | anon[27] -- other             | anon[27]                    |
| |\                 +----------------+-------------------------------+-----------------------------+
| | +Expand(All)     |             15 | anon[66], anon[80] -- other   | (other)-[:WORKS_IN]->()     |
| | |                +----------------+-------------------------------+-----------------------------+
| | +Argument        |             14 | other                         |                             |
| |                  +----------------+-------------------------------+-----------------------------+
| +LetSemiApply      |             14 | anon[27] -- other             |                             |
| |\                 +----------------+-------------------------------+-----------------------------+
| | +Expand(All)     |              2 | anon[35], anon[53] -- other   | (other)-[:FRIENDS_WITH]->() |
| | |                +----------------+-------------------------------+-----------------------------+
| | +Argument        |             14 | other                         |                             |
| |                  +----------------+-------------------------------+-----------------------------+
| +NodeByLabelScan   |             14 | other                         | :Person                     |
+--------------------+----------------+-------------------------------+-----------------------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MATCH (other:Person) WHERE (other)-[:FRIENDS_WITH]->() OR (other)-[:WORKS_IN]->() RETURN other.name

3.7.3.5. LetAntiSemiApply

Tests for the absence of a pattern predicate. When a query contains multiple pattern predicates LetAntiSemiApply will be used to evaluate the first of these. It will record the result of evaluating the predicate but will leave any filtering to another operator. The following query will find all the people who don’t have any friends or who work somewhere.

Query. 

MATCH (other:Person)
WHERE NOT ((other)-[:FRIENDS_WITH]->()) OR (other)-[:WORKS_IN]->()
RETURN other.name

Finds all the people who don’t have any friends or who work somewhere. The LetAntiSemiApply operator will be used to check for the absence of the FRIENDS_WITH relationship from each person.

Query plan. 

+--------------------+----------------+-------------------------------+-----------------------------+
| Operator           | Estimated Rows | Variables                     | Other                       |
+--------------------+----------------+-------------------------------+-----------------------------+
| +ProduceResults    |             11 | other.name                    | other.name                  |
| |                  +----------------+-------------------------------+-----------------------------+
| +Projection        |             11 | other.name -- anon[31], other | other.name                  |
| |                  +----------------+-------------------------------+-----------------------------+
| +SelectOrSemiApply |             11 | anon[31] -- other             | anon[31]                    |
| |\                 +----------------+-------------------------------+-----------------------------+
| | +Expand(All)     |             15 | anon[71], anon[85] -- other   | (other)-[:WORKS_IN]->()     |
| | |                +----------------+-------------------------------+-----------------------------+
| | +Argument        |             14 | other                         |                             |
| |                  +----------------+-------------------------------+-----------------------------+
| +LetAntiSemiApply  |             14 | anon[31] -- other             |                             |
| |\                 +----------------+-------------------------------+-----------------------------+
| | +Expand(All)     |              2 | anon[39], anon[57] -- other   | (other)-[:FRIENDS_WITH]->() |
| | |                +----------------+-------------------------------+-----------------------------+
| | +Argument        |             14 | other                         |                             |
| |                  +----------------+-------------------------------+-----------------------------+
| +NodeByLabelScan   |             14 | other                         | :Person                     |
+--------------------+----------------+-------------------------------+-----------------------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MATCH (other:Person) WHERE NOT((other)-[:FRIENDS_WITH]->()) OR (other)-[:WORKS_IN]->() RETURN other.name

3.7.3.6. SelectOrSemiApply

Tests for the existence of a pattern predicate and evaluates a predicate. This operator allows for the mixing of normal predicates and pattern predicates that check for the existence of a pattern. First the normal expression predicate is evaluated, and only if it returns false the costly pattern predicate evaluation is performed.

Query. 

MATCH (other:Person)
WHERE other.age > 25 OR (other)-[:FRIENDS_WITH]->()
RETURN other.name

Finds the names of all people who have friends, or are older than 25.

Query plan. 

+--------------------+----------------+-----------------------------+-----------------------------+
| Operator           | Estimated Rows | Variables                   | Other                       |
+--------------------+----------------+-----------------------------+-----------------------------+
| +ProduceResults    |             11 | other.name                  | other.name                  |
| |                  +----------------+-----------------------------+-----------------------------+
| +Projection        |             11 | other.name -- other         | other.name                  |
| |                  +----------------+-----------------------------+-----------------------------+
| +SelectOrSemiApply |             11 | other                       | other.age > {  AUTOINT0}    |
| |\                 +----------------+-----------------------------+-----------------------------+
| | +Expand(All)     |              2 | anon[53], anon[71] -- other | (other)-[:FRIENDS_WITH]->() |
| | |                +----------------+-----------------------------+-----------------------------+
| | +Argument        |             14 | other                       |                             |
| |                  +----------------+-----------------------------+-----------------------------+
| +NodeByLabelScan   |             14 | other                       | :Person                     |
+--------------------+----------------+-----------------------------+-----------------------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MATCH (other:Person) WHERE other.age > 25 OR (other)-[:FRIENDS_WITH]->() RETURN other.name

3.7.3.7. SelectOrAntiSemiApply

Tests for the absence of a pattern predicate and evaluates a predicate.

Query. 

MATCH (other:Person)
WHERE other.age > 25 OR NOT (other)-[:FRIENDS_WITH]->()
RETURN other.name

Finds the names of all people who do not have friends, or are older than 25.

Query plan. 

+------------------------+----------------+-----------------------------+-----------------------------+
| Operator               | Estimated Rows | Variables                   | Other                       |
+------------------------+----------------+-----------------------------+-----------------------------+
| +ProduceResults        |              4 | other.name                  | other.name                  |
| |                      +----------------+-----------------------------+-----------------------------+
| +Projection            |              4 | other.name -- other         | other.name                  |
| |                      +----------------+-----------------------------+-----------------------------+
| +SelectOrAntiSemiApply |              4 | other                       | other.age > {  AUTOINT0}    |
| |\                     +----------------+-----------------------------+-----------------------------+
| | +Expand(All)         |              2 | anon[57], anon[75] -- other | (other)-[:FRIENDS_WITH]->() |
| | |                    +----------------+-----------------------------+-----------------------------+
| | +Argument            |             14 | other                       |                             |
| |                      +----------------+-----------------------------+-----------------------------+
| +NodeByLabelScan       |             14 | other                       | :Person                     |
+------------------------+----------------+-----------------------------+-----------------------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MATCH (other:Person) WHERE other.age > 25 OR NOT (other)-[:FRIENDS_WITH]->() RETURN other.name

3.7.3.8. ConditionalApply

Checks whether a variable is not null, and if so the right-hand side will be executed.

Query. 

MERGE (p:Person { name: 'Andres' })
ON MATCH SET p.exists = TRUE

Looks for the existence of a person called Andres, and if found sets the exists property to true.

Query plan. 

+-----------------------+----------------+-----------+---------------+
| Operator              | Estimated Rows | Variables | Other         |
+-----------------------+----------------+-----------+---------------+
| +ProduceResults       |              1 |           |               |
| |                     +----------------+-----------+---------------+
| +EmptyResult          |                |           |               |
| |                     +----------------+-----------+---------------+
| +AntiConditionalApply |              1 | p         |               |
| |\                    +----------------+-----------+---------------+
| | +MergeCreateNode    |              1 | p         |               |
| |                     +----------------+-----------+---------------+
| +ConditionalApply     |              1 | p         |               |
| |\                    +----------------+-----------+---------------+
| | +SetNodeProperty    |              1 | p         |               |
| | |                   +----------------+-----------+---------------+
| | +Argument           |              1 | p         |               |
| |                     +----------------+-----------+---------------+
| +Optional             |              1 | p         |               |
| |                     +----------------+-----------+---------------+
| +NodeIndexSeek        |              1 | p         | :Person(name) |
+-----------------------+----------------+-----------+---------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MERGE (p:Person {name: 'Andres'}) ON MATCH SET p.exists = true

3.7.3.9. AntiConditionalApply

Checks whether a variable is null, and if so the right-hand side will be executed.

Query. 

MERGE (p:Person { name: 'Andres' })
ON CREATE SET p.exists = TRUE

Looks for the existence of a person called Andres, and if not found, creates one and sets the exists property to true.

Query plan. 

+-----------------------+----------------+-----------+---------------+
| Operator              | Estimated Rows | Variables | Other         |
+-----------------------+----------------+-----------+---------------+
| +ProduceResults       |              1 |           |               |
| |                     +----------------+-----------+---------------+
| +EmptyResult          |                |           |               |
| |                     +----------------+-----------+---------------+
| +AntiConditionalApply |              1 | p         |               |
| |\                    +----------------+-----------+---------------+
| | +SetNodeProperty    |              1 | p         |               |
| | |                   +----------------+-----------+---------------+
| | +MergeCreateNode    |              1 | p         |               |
| |                     +----------------+-----------+---------------+
| +Optional             |              1 | p         |               |
| |                     +----------------+-----------+---------------+
| +NodeIndexSeek        |              1 | p         | :Person(name) |
+-----------------------+----------------+-----------+---------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MERGE (p:Person {name: 'Andres'}) ON CREATE SET p.exists = true

3.7.3.10. AssertSameNode

This operator is used to ensure that no uniqueness constraints are violated.

Query. 

MERGE (t:Team { name: 'Engineering', id: 42 })

Looks for the existence of a team with the supplied name and id, and if one does not exist, it will be created. Due to the existence of two uniqueness constraints on :Team(name) and :Team(id), any node that would be found by the `UniqueIndexSeek`s must be the very same node, or the constraints would be violated.

Query plan. 

+---------------------------------+----------------+-----------+-------------+
| Operator                        | Estimated Rows | Variables | Other       |
+---------------------------------+----------------+-----------+-------------+
| +ProduceResults                 |              1 |           |             |
| |                               +----------------+-----------+-------------+
| +EmptyResult                    |                |           |             |
| |                               +----------------+-----------+-------------+
| +AntiConditionalApply           |              1 | t         |             |
| |\                              +----------------+-----------+-------------+
| | +MergeCreateNode              |              1 | t         |             |
| |                               +----------------+-----------+-------------+
| +Optional                       |              1 | t         |             |
| |                               +----------------+-----------+-------------+
| +AssertSameNode                 |              0 | t         |             |
| |\                              +----------------+-----------+-------------+
| | +NodeUniqueIndexSeek(Locking) |              1 | t         | :Team(id)   |
| |                               +----------------+-----------+-------------+
| +NodeUniqueIndexSeek(Locking)   |              1 | t         | :Team(name) |
+---------------------------------+----------------+-----------+-------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MERGE (t:Team {name: 'Engineering', id: 42})

3.7.3.11. NodeHashJoin

Using a hash table, a NodeHashJoin joins the input coming from the left with the input coming from the right.

Query. 

MATCH (andy:Person { name:'Andreas' })-[:WORKS_IN]->(loc)<-[:WORKS_IN]-(matt:Person { name:'Mattis' })
RETURN loc.name

Returns the name of the location where the matched persons both work.

Query plan. 

+------------------+----------------+-------------------------------------------------+---------------------------+
| Operator         | Estimated Rows | Variables                                       | Other                     |
+------------------+----------------+-------------------------------------------------+---------------------------+
| +ProduceResults  |             10 | loc.name                                        | loc.name                  |
| |                +----------------+-------------------------------------------------+---------------------------+
| +Projection      |             10 | loc.name -- anon[37], anon[56], andy, loc, matt | loc.name                  |
| |                +----------------+-------------------------------------------------+---------------------------+
| +Filter          |             10 | anon[37], anon[56], andy, loc, matt             | NOT(anon[37] == anon[56]) |
| |                +----------------+-------------------------------------------------+---------------------------+
| +NodeHashJoin    |             10 | anon[37], andy -- anon[56], loc, matt           | loc                       |
| |\               +----------------+-------------------------------------------------+---------------------------+
| | +Expand(All)   |             19 | anon[56], loc -- matt                           | (matt)-[:WORKS_IN]->(loc) |
| | |              +----------------+-------------------------------------------------+---------------------------+
| | +NodeIndexSeek |              1 | matt                                            | :Person(name)             |
| |                +----------------+-------------------------------------------------+---------------------------+
| +Expand(All)     |             19 | anon[37], loc -- andy                           | (andy)-[:WORKS_IN]->(loc) |
| |                +----------------+-------------------------------------------------+---------------------------+
| +NodeIndexSeek   |              1 | andy                                            | :Person(name)             |
+------------------+----------------+-------------------------------------------------+---------------------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MATCH (london:Location {name: 'London'}), (person:Person {name: 'Pontus'}) FOREACH(x in range(0,250) | CREATE (person)-[:WORKS_IN]->(london) ) MATCH (andy:Person {name:'Andreas'})-[:WORKS_IN]->(loc)<-[:WORKS_IN]-(matt:Person {name:'Mattis'}) RETURN loc.name

3.7.3.12. Triadic

Triadic is used to solve triangular queries, such as the very common 'find my friend-of-friends that are not already my friend'. It does so by putting all the friends in a set, and use that set to check if the friend-of-friends are already connected to me.

Query. 

MATCH (me:Person)-[:FRIENDS_WITH]-()-[:FRIENDS_WITH]-(other)
WHERE NOT (me)-[:FRIENDS_WITH]-(other)
RETURN other.name

Finds the names of all friends of my friends that are not already my friends.

Query plan. 

+-------------------+----------------+-------------------------------------------------------+----------------------------+
| Operator          | Estimated Rows | Variables                                             | Other                      |
+-------------------+----------------+-------------------------------------------------------+----------------------------+
| +ProduceResults   |              0 | other.name                                            | other.name                 |
| |                 +----------------+-------------------------------------------------------+----------------------------+
| +Projection       |              0 | other.name -- anon[18], anon[35], anon[37], me, other | other.name                 |
| |                 +----------------+-------------------------------------------------------+----------------------------+
| +TriadicSelection |              0 | anon[18], anon[35], me -- anon[37], other             | me, anon[35], other        |
| |\                +----------------+-------------------------------------------------------+----------------------------+
| | +Filter         |              0 | anon[37], other                                       | NOT(anon[18] == anon[37])  |
| | |               +----------------+-------------------------------------------------------+----------------------------+
| | +Expand(All)    |              0 | anon[37], other                                       | ()-[:FRIENDS_WITH]-(other) |
| | |               +----------------+-------------------------------------------------------+----------------------------+
| | +Argument       |              4 |                                                       |                            |
| |                 +----------------+-------------------------------------------------------+----------------------------+
| +Expand(All)      |              4 | anon[18], anon[35] -- me                              | (me)-[:FRIENDS_WITH]-()    |
| |                 +----------------+-------------------------------------------------------+----------------------------+
| +NodeByLabelScan  |             14 | me                                                    | :Person                    |
+-------------------+----------------+-------------------------------------------------------+----------------------------+

Total database accesses: ?

Try this query live.  CREATE CONSTRAINT ON (team:Team) ASSERT team.name is UNIQUE CREATE CONSTRAINT ON (team:Team) ASSERT team.id is UNIQUE CREATE INDEX ON :Location(name) CREATE INDEX ON :Person(name) CREATE (me:Person {name:'me'}) CREATE (andres:Person {name:'Andres'}) CREATE (andreas:Person {name:'Andreas'}) CREATE (mattias:Person {name:'Mattias'}) CREATE (lovis:Person {name:'Lovis'}) CREATE (pontus:Person {name:'Pontus'}) CREATE (max:Person {name:'Max'}) CREATE (konstantin:Person {name:'Konstantin'}) CREATE (stefan:Person {name:'Stefan'}) CREATE (mats:Person {name:'Mats'}) CREATE (petra:Person {name:'Petra'}) CREATE (craig:Person {name:'Craig'}) CREATE (steven:Person {name:'Steven'}) CREATE (chris:Person {name:'Chris'}) CREATE (london:Location {name:'London'}) CREATE (malmo:Location {name:'Malmo'}) CREATE (sf:Location {name:'San Francisco'}) CREATE (berlin:Location {name:'Berlin'}) CREATE (newyork:Location {name:'New York'}) CREATE (kuala:Location {name:'Kuala Lumpur'}) CREATE (stockholm:Location {name:'Stockholm'}) CREATE (paris:Location {name:'Paris'}) CREATE (madrid:Location {name:'Madrid'}) CREATE (rome:Location {name:'Rome'}) CREATE (england:Country {name:'England'}) CREATE (field:Team {name:'Field'}) CREATE (engineering:Team {name:'Engineering', id: 42}) CREATE (sales:Team {name:'Sales'}) CREATE (monads:Team {name:'Team Monads'}) CREATE (birds:Team {name:'Team Enlightened Birdmen'}) CREATE (quality:Team {name:'Team Quality'}) CREATE (rassilon:Team {name:'Team Rassilon'}) CREATE (executive:Team {name:'Team Executive'}) CREATE (remoting:Team {name:'Team Remoting'}) CREATE (other:Team {name:'Other'}) CREATE (me)-[:WORKS_IN {duration: 190}]->(london) CREATE (andreas)-[:WORKS_IN {duration: 187}]->(london) CREATE (andres)-[:WORKS_IN {duration: 150}]->(london) CREATE (mattias)-[:WORKS_IN {duration: 230}]->(london) CREATE (lovis)-[:WORKS_IN {duration: 230}]->(sf) CREATE (pontus)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (max)-[:WORKS_IN {duration: 230}]->(newyork) CREATE (konstantin)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(london) CREATE (stefan)-[:WORKS_IN {duration: 230}]->(berlin) CREATE (mats)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (petra)-[:WORKS_IN {duration: 230}]->(london) CREATE (craig)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (steven)-[:WORKS_IN {duration: 230}]->(malmo) CREATE (chris)-[:WORKS_IN {duration: 230}]->(madrid) CREATE (london)-[:IN]->(england) CREATE (me)-[:FRIENDS_WITH]->(andres) CREATE (andres)-[:FRIENDS_WITH]->(andreas) MATCH (me:Person)-[:FRIENDS_WITH]-()-[:FRIENDS_WITH]-(other) WHERE NOT (me)-[:FRIENDS_WITH]-(other) RETURN other.name

3.7.4. Row operators

These operators take rows produced by another operator and transform them to a different set of rows

3.7.4.1. Eager

For isolation purposes this operator makes sure that operations that affect subsequent operations are executed fully for the whole dataset before continuing execution. Otherwise it could trigger endless loops, matching data again, that was just created. The Eager operator can cause high memory usage when importing data or migrating graph structures. In such cases split up your operations into simpler steps e.g. you can import nodes and relationships separately. Alternatively return the records to be updated and run an update statement afterwards.

Query. 

MATCH (a)-[r]-(b)
DELETE r,a,b
MERGE ()

Query Plan. 

+-------------------------+----------------+------+---------+---------------------+--------------+
| Operator                | Estimated Rows | Rows | DB Hits | Variables           | Other        |
+-------------------------+----------------+------+---------+---------------------+--------------+
| +ProduceResults         |              1 |    0 |       0 |                     |              |
| |                       +----------------+------+---------+---------------------+--------------+
| +EmptyResult            |                |    0 |       0 |                     |              |
| |                       +----------------+------+---------+---------------------+--------------+
| +Apply                  |              1 |  504 |       0 | a, b, r -- anon[38] |              |
| |\                      +----------------+------+---------+---------------------+--------------+
| | +AntiConditionalApply |              1 |  504 |       0 | anon[38]            |              |
| | |\                    +----------------+------+---------+---------------------+--------------+
| | | +MergeCreateNode    |              1 |    0 |       0 | anon[38]            |              |
| | |                     +----------------+------+---------+---------------------+--------------+
| | +Optional             |             35 |  504 |       0 | anon[38]            |              |
| | |                     +----------------+------+---------+---------------------+--------------+
| | +AllNodesScan         |             35 |  504 |     540 | anon[38]            |              |
| |                       +----------------+------+---------+---------------------+--------------+
| +Eager                  |                |   36 |       0 | a, b, r             |              |
| |                       +----------------+------+---------+---------------------+--------------+
| +Delete(3)              |             36 |   36 |      39 | a, b, r             |              |
| |                       +----------------+------+---------+---------------------+--------------+
| +Eager                  |                |   36 |       0 | a, b, r             |              |
| |                       +----------------+------+---------+---------------------+--------------+
| +Expand(All)            |             36 |   36 |      71 | a, r -- b           | (b)-[r:]-(a) |
| |                       +----------------+------+---------+---------------------+--------------+
| +AllNodesScan           |             35 |   35 |      36 | b                   |              |
+-------------------------+----------------+------+---------+---------------------+--------------+

Total database accesses: 686

3.7.4.2. Distinct

Removes duplicate rows from the incoming stream of rows.

Query. 

MATCH (l:Location)<-[:WORKS_IN]-(p:Person)
RETURN DISTINCT l

Query Plan. 

+------------------+----------------+------+---------+------------------+----------------------+
| Operator         | Estimated Rows | Rows | DB Hits | Variables        | Other                |
+------------------+----------------+------+---------+------------------+----------------------+
| +ProduceResults  |             14 |    6 |       0 | l                | l                    |
| |                +----------------+------+---------+------------------+----------------------+
| +Distinct        |             14 |    6 |       0 | l                | l                    |
| |                +----------------+------+---------+------------------+----------------------+
| +Filter          |             15 |   15 |      15 | anon[19], l, p   | p:Person             |
| |                +----------------+------+---------+------------------+----------------------+
| +Expand(All)     |             15 |   15 |      25 | anon[19], p -- l | (l)<-[:WORKS_IN]-(p) |
| |                +----------------+------+---------+------------------+----------------------+
| +NodeByLabelScan |             10 |   10 |      11 | l                | :Location            |
+------------------+----------------+------+---------+------------------+----------------------+

Total database accesses: 51

3.7.4.3. Eager Aggregation

Eagerly loads underlying results and stores it in a hash-map, using the grouping keys as the keys for the map.

Query. 

MATCH (l:Location)<-[:WORKS_IN]-(p:Person)
RETURN l.name AS location, COLLECT(p.name) AS people

Query Plan. 

+-------------------+----------------+------+---------+----------------------------+----------------------+
| Operator          | Estimated Rows | Rows | DB Hits | Variables                  | Other                |
+-------------------+----------------+------+---------+----------------------------+----------------------+
| +ProduceResults   |              4 |    6 |       0 | location, people           | location, people     |
| |                 +----------------+------+---------+----------------------------+----------------------+
| +EagerAggregation |              4 |    6 |      15 | people -- location         | location             |
| |                 +----------------+------+---------+----------------------------+----------------------+
| +Projection       |             15 |   15 |      15 | location -- anon[19], l, p | l.name; p            |
| |                 +----------------+------+---------+----------------------------+----------------------+
| +Filter           |             15 |   15 |      15 | anon[19], l, p             | p:Person             |
| |                 +----------------+------+---------+----------------------------+----------------------+
| +Expand(All)      |             15 |   15 |      25 | anon[19], p -- l           | (l)<-[:WORKS_IN]-(p) |
| |                 +----------------+------+---------+----------------------------+----------------------+
| +NodeByLabelScan  |             10 |   10 |      11 | l                          | :Location            |
+-------------------+----------------+------+---------+----------------------------+----------------------+

Total database accesses: 81

3.7.4.4. Node Count From Count Store

Use the count store to answer questions about node counts. This is much faster than eager aggregation which achieves the same result by actually counting. However the count store only saves a limited range of combinations, so eager aggregation will still be used for more complex queries. For example, we can get counts for all nodes, and nodes with a label, but not nodes with more than one label.

Query. 

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

Query Plan. 

+--------------------------+----------------+------+---------+-----------+------------------------------+
| Operator                 | Estimated Rows | Rows | DB Hits | Variables | Other                        |
+--------------------------+----------------+------+---------+-----------+------------------------------+
| +ProduceResults          |              4 |    1 |       0 | people    | people                       |
| |                        +----------------+------+---------+-----------+------------------------------+
| +NodeCountFromCountStore |              4 |    1 |       0 | people    | count( (:Person) ) AS people |
+--------------------------+----------------+------+---------+-----------+------------------------------+

Total database accesses: 0

3.7.4.5. Relationship Count From Count Store

Use the count store to answer questions about relationship counts. This is much faster than eager aggregation which achieves the same result by actually counting. However the count store only saves a limited range of combinations, so eager aggregation will still be used for more complex queries. For example, we can get counts for all relationships, relationships with a type, relationships with a label on one end, but not relationships with labels on both end nodes.

Query. 

MATCH (p:Person)-[r:WORKS_IN]->()
RETURN count(r) AS jobs

Query Plan. 

+----------------------------------+----------------+------+---------+-----------+--------------------------------------------+
| Operator                         | Estimated Rows | Rows | DB Hits | Variables | Other                                      |
+----------------------------------+----------------+------+---------+-----------+--------------------------------------------+
| +ProduceResults                  |              4 |    1 |       0 | jobs      | jobs                                       |
| |                                +----------------+------+---------+-----------+--------------------------------------------+
| +RelationshipCountFromCountStore |              4 |    1 |       1 | jobs      | count( (:Person)-[:WORKS_IN]->() ) AS jobs |
+----------------------------------+----------------+------+---------+-----------+--------------------------------------------+

Total database accesses: 1

3.7.4.6. Filter

Filters each row coming from the child operator, only passing through rows that evaluate the predicates to TRUE.

Query. 

MATCH (p:Person)
WHERE p.name =~ "^a.*"
RETURN p

Query Plan. 

+------------------+----------------+------+---------+-----------+-----------------------------+
| Operator         | Estimated Rows | Rows | DB Hits | Variables | Other                       |
+------------------+----------------+------+---------+-----------+-----------------------------+
| +ProduceResults  |             14 |    0 |       0 | p         | p                           |
| |                +----------------+------+---------+-----------+-----------------------------+
| +Filter          |             14 |    0 |      14 | p         | p.name ~= /{  AUTOSTRING0}/ |
| |                +----------------+------+---------+-----------+-----------------------------+
| +NodeByLabelScan |             14 |   14 |      15 | p         | :Person                     |
+------------------+----------------+------+---------+-----------+-----------------------------+

Total database accesses: 29

3.7.4.7. Limit

Returns the first 'n' rows from the incoming input.

Query. 

MATCH (p:Person)
RETURN p
LIMIT 3

Query Plan. 

+------------------+----------------+------+---------+-----------+------------+
| Operator         | Estimated Rows | Rows | DB Hits | Variables | Other      |
+------------------+----------------+------+---------+-----------+------------+
| +ProduceResults  |              3 |    3 |       0 | p         | p          |
| |                +----------------+------+---------+-----------+------------+
| +Limit           |              3 |    3 |       0 | p         | Literal(3) |
| |                +----------------+------+---------+-----------+------------+
| +NodeByLabelScan |             14 |    3 |       4 | p         | :Person    |
+------------------+----------------+------+---------+-----------+------------+

Total database accesses: 4