0% found this document useful (0 votes)
5 views36 pages

Understanding Graph Databases Basics

Chapter 3 provides an overview of graph databases, focusing on their structure, which consists of nodes, relationships, properties, and labels. It introduces Neo4j as a prominent graph database and explains how to create and query data using Cypher, the query language designed for graph databases. The chapter covers various aspects of graph data management, including indexing, filtering, and organizing nodes and relationships.

Uploaded by

bakaazzedine223
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views36 pages

Understanding Graph Databases Basics

Chapter 3 provides an overview of graph databases, focusing on their structure, which consists of nodes, relationships, properties, and labels. It introduces Neo4j as a prominent graph database and explains how to create and query data using Cypher, the query language designed for graph databases. The chapter covers various aspects of graph data management, including indexing, filtering, and organizing nodes and relationships.

Uploaded by

bakaazzedine223
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Chapter 3

Graph Databases

CHAPTER 3 1
1. What is a Graph Database?
A graph database stores data in a graph, the most generic of data structures, capable of elegantly
representing any kind of data in a highly accessible way. Let’s follow along some graphs, using
themto express graph concepts. We’ll “read” a graph by following arrows around the diagram to
form sentences.

• A Graph contains Nodes and Relationships


“A Graph —records data in→ Nodes —which have→ Properties”

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

• Relationships organize the Graph


“Nodes —are organized by→ Relationships —which also have→ Properties”

Relationships organize Nodes into arbitrary structures, allowing a Graph to resemble a


List, a Tree,a Map, or a compound Entity – any of which can be combined into yet more
complex, richly inter-connected structures.

• Labels group the Nodes


“Nodes —are grouped by→ Labels —into→ Sets”

Labels are a means of grouping the nodes in the graph. They can be used to restrict queries
to subsetsof the graph, as well as enabling optional model constraints and indexing rules.
• Indexes look-up Nodes or Relationships
“An Index —maps from→ Properties —to either→ Nodes or Relationships”

Often, you want to find a specific Node or Relationship according to a Property it has.
Rather than traversing the entire graph, use an Index to perform a look-up, for questions like
“find the Account forusername master-of-graphs.”

Neo4j is a Graph Database


“A Graph Database —manages a→ Graph and —also manages related→ Indexes”

Neo4j is a commercially supported open-source graph database. It was designed and built
from the ground-up to be a reliable database, optimized for graph structures instead of
tables. Working with Neo4j, your application gets all the expressiveness of a graph, with
all the dependability you expectout of a database.

CHAPTER 3 2
1.1 . Nodes
The fundamental units that form a graph are nodes and relationships. In Neo4j, both
nodes andrelationships can contain properties.
Nodes are often used to represent entities, but depending on the domain relationships may
be used forthat purpose as well.
Apart from properties and relationships, nodes can also be labeled with zero or more labels.

A Node

can have can have

Relationships can have Labels

can have

Properties

1.2. Relationships
Relationships between nodes are a key part of a graph database. They allow for finding
related [Link] like nodes, relationships can have properties.

A Relationship

has a has a has a can have

Start node End node Relationship type Properties

uniquely identified by

Nam e

CHAPTER 3 3
A relationship connects two nodes, and is guaranteed to have valid start and end nodes.

relationship
Start node End node

As relationships are always directed, they can be viewed as outgoing or incoming relative to a
node,which is useful when traversing the graph:

incom ing relationship outgoing relationship


Node

Rela%onships are equally well traversed in either direc%on. This means that there is no need
to addduplicate rela<onships in the opposite direc<on (with regard to traversal or
performance).
While relationships always have a direction, you can ignore the direction where it is not useful in
yourapplication.
Note that a node can have relationships to itself as well:

Node loop

The following example shows a simple social network with two relationship types.

Maja Alice

follows follows follows

Oscar

blocks

William

CHAPTER 3 4
1.3. Properties
Both nodes and relationships can have properties. Properties are key-value pairs where the key is a
string. Property values can be either a primitive or an array of one primitive type. For example String,
int and int[] values are valid for properties.

A Property

has a has a

Value Key

1.4. Labels
A label is a named graph construct that is used to group nodes into sets; all nodes labeled with the
same label belongs to the same set. Many database queries can work with these sets instead of the
whole graph, making queries easier to write and more efficient. A node may be labeled with any
number of labels, including none, making labels an optional addition to the graph.

A Label

has a groups

Nam e Node

Labels are used when defining contraints and adding indexes for properties.
An example would be a label named User that you label all your nodes representing users with. With
that in place, you can ask Neo4j to perform operations only on your user nodes, such as finding all
users with a given name.
However, you can use labels for much more. For instance, since labels can be added and removed
during runtime, they can be used to mark temporary states for your nodes. You might create an
Offline label for phones that are offline, a Happy label for happy pets, and so on.

2. General Clauses

2.1. Return
In the RETURN part of your query, you define which parts of the pattern you are interested in. It can be nodes,

CHAPTER 3 5
relationships, or properties on these.
nam e = 'A'
happy = 'Yes!'
age = 55

KNOWS BLOCKS

nam e = 'B'

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

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

Return property
To return a property, use the dot separator, like this:
Query.
MATCH (n { name: "A" })
RETURN [Link]

CHAPTER 3 6
The value of the property name gets returned.

Result
[Link]
"A"
1 row

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

2.2. 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 [Link]

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

Result
[Link]
55
<null>
2 rows

CHAPTER 3 7
2.3. Other expressions
Any expression can be used as a return item — literals, predicates, properties, functions, and everything
else.
Query.
MATCH (a { name: "A" })
RETURN [Link] > 30, "I'm a literal",(a)-->()

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

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

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

2.5. Order by
To sort the output, use the ORDER BY clause. Note that you can not sort on nodes or relationships, just on
properties on these. ORDER BY relies on comparisons to sort the output.

CHAPTER 3 8
nam e = 'A'
age = 34
length = 170

KNOWS

nam e = 'B'
age = 34

KNOWS

nam e = 'C'
age = 32
length = 185

Order nodes by property ORDER BY

is used to sort the output. Query.


MATCH (n)
RETURN n
ORDER BY [Link]

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

Order nodes by multiple properties


You can order by multiple properties by stating each identifier in the ORDER BY clause. Cypher will sort the
result by the first identifier listed, and for equals values, go to the next property in the ORDER BY clause, and
so on.
Query.
MATCH (n)
RETURN n
ORDER BY [Link], [Link]

CHAPTER 3 9
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

Order nodes in descending order


By adding DESC[ENDING] after the identifier to sort on, the sort will be done in reverse order.
Query.
MATCH (n)
RETURN n
ORDER BY [Link] 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

2.6. Using
If you do not specify an explicit START clause, Cypher needs to infer where in the graph to start
your query. This is done by looking at the WHERE clause and the MATCH clause and using that
information to find a useful index.
This index might not be the best choice though — sometimes multiple indexes could be used, and
Cypher has picked the wrong one (from a performance point of view).
You can force Cypher to use a specific starting point by using the USING clause. This is called giving
Cypher an index hint.
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. It will force Cypher to not use an
index that could have been used, and instead do a label scan.

Query using an index hint


To query using an index hint, use USING INDEX.

CHAPTER 3 10
Query.
MATCH (n:Swedish)
USING INDEX n:Swedish(surname)
WHERE [Link] = 'Taylor'
RETURN n

The query result is returned as usual.

Result
n
Node[3]{name:"Andres", age:36, awesome:true, surname:"Taylor"}
1 row

Query using mul3ple index hints To query


using multiple index hints, use USING INDEX.
Query.
MATCH (m:German)-->(n:Swedish)
USING INDEX m:German(surname)
USING INDEX n:Swedish(surname)
WHERE [Link] = 'Plantikow' AND [Link] = 'Taylor'
RETURN m

The query result is returned as usual.

Result
m
Node[1]{name:"Stefan", surname:"Plantikow"}
1 row

2.7. Where
WHERE is not a clause in it’s own right, rather, it’s part of MATCH.

Swedish

nam e = 'Andres'
age = 36
belt = 'white'

KNOWS KNOWS

nam e = 'Tobias' nam e = 'Peter'


age = 25 age = 34

Basic usage

CHAPTER 3 11
Query.
MATCH (n)
WHERE [Link] = 'Peter' XOR ([Link] < 30 AND [Link] = "Tobias") OR NOT ([Link] = "Tobias" OR
[Link]="Peter")
RETURN n

This query shows how boolean operators can be used.

Result
n
Node[0]{name:"Tobias", age:25}
Node[1]{name:"Peter", age:34}
Node[2]{name:"Andres", age:36, belt:"white"}
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[2]{name:"Andres", age:36, belt:"white"}
1 row

Filter on node property


To filter on a property, write your clause after the WHERE keyword. Filtering on relationship properties
works just the same way.
Query.
MATCH (n)
WHERE [Link] < 30
RETURN n

The "Tobias" node will be returned.

Result
n
Node[0]{name:"Tobias", age:25}
1 row

Property exists

CHAPTER 3 12
To only include nodes/relationships that have a property, use the HAS() function and just write out the
identifier and the property you expect it to have.
Query.
MATCH (n)
WHERE HAS ([Link])
RETURN n

The node named "Andres" is returned.

Result
n
Node[2]{name:"Andres", age:36, belt:"white"}
1 row

3. Getting deeper with Cypher


Cypher is the name of the 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.

3.1. Create nodes and relationships


Create a node for the actor Tom Hanks:
CREATE (n:Actor { name:"Tom Hanks" });

Let’s find the node we created:


MATCH (actor:Actor { name: "Tom Hanks" })
RETURN actor;

Now let’s create a movie and connect it to the Tom Hanks node with an ACTED_IN relationship:
MATCH (actor:Actor)
WHERE [Link] = "Tom Hanks"
CREATE (movie:Movie { title:'Sleepless IN Seattle' })
CREATE (actor)-[:ACTED_IN]->(movie);

Using a WHERE clause in the query above to get the Tom Hanks node does the same thing as the
pattern in the MATCH clause of the previous query.

CHAPTER 3 13
This is how our graph looks now:

Actor

nam e = 'Tom Hanks'

ACTED_IN

Movie

t it le = 'Sleepless in Seattle'

We can do more of the work in a single clause. CREATE UNIQUE will make sure we don’t create
duplicate patterns. Using this: [r:ACTED_IN] lets us return the relationship.
MATCH (actor:Actor { name: "Tom Hanks" })
CREATE UNIQUE (actor)-[r:ACTED_IN]->(movie:Movie { title:"Forrest Gump" })
RETURN r;

Set a property on a node:


MATCH (actor:Actor { name: "Tom Hanks" })
SET [Link] = 1944
RETURN [Link], [Link];

The labels Actor and Movie help us organize the graph. Let’s list all Movie nodes:
MATCH (movie:Movie)
RETURN movie AS `All Movies`;

All Movies
Node[1]{title:"Sleepless in Seattle"}
Node[2]{title:"Forrest Gump"}
2 rows

3.2. Relationship basics


The following graph is used for the examples of this section :

CHAPTER 3 14
4.

Outgoing relationships
When the direction of a relationship is interesting, it is shown by using --> or <--, like this:
Query.
MATCH (martin { name:'Martin Sheen' })-->(movie)
RETURN [Link]

Returns nodes connected to Martin by outgoing relationships.

Result
[Link]
"Wall Street"
"The American President"
2 rows

Directed relationships and identifier


If an identifier is needed, either for filtering on properties of the relationship, or to return the
relationship, this is how you introduce the identifier.
Query.
MATCH (martin { name:'Martin Sheen' })-[r]->(movie)
RETURN r

Returns all outgoing relationships from Martin.

Result
r
:ACTED_IN[1]{}
:ACTED_IN[3]{}
2 rows

Match by relationship type

CHAPTER 3 15
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 { title:'Wall Street' })<-[:ACTED_IN]-(actor)


RETURN actor

Returns nodes that ACTED_IN Wall Street.

Result
actor
Node[1]{name:"Charlie Sheen"}
Node[2]{name:"Martin Sheen"}
Node[6]{name:"Michael Douglas"}
3 rows

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

Returns nodes with a ACTED_IN or DIRECTED relaNonship to Wall Street.

Result
person
Node[0]{name:"Oliver Stone"}
Node[1]{name:"Charlie Sheen"}
Node[2]{name:"Martin Sheen"}
Node[6]{name:"Michael Douglas"}
4 rows

Match by relationship type and use an identifier


If you both want to introduce an identifier to hold the relationship, and specify the relationship
type you want, just add them both, like this.
Query.
MATCH (wallstreet { title:'Wall Street' })<-[r:ACTED_IN]-(actor)
RETURN r

Returns nodes that ACTED_IN Wall Street.

Result

CHAPTER 3 16
r
:ACTED_IN[0]{}
:ACTED_IN[1]{}
:ACTED_IN[2]{}
3 rows

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 charlie,movie,director

Returns the three nodes in the path.

Result
charlie movie director
Node[1]{name:"Charlie Sheen"} Node[4]{name:"WallStreet", Node[0]{name:"Oliver Stone"}
title:"Wall Street"}
1 row

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.

Result
a b
Node[1]{name:"Charlie Sheen"} Node[4]{name:"WallStreet", title:"Wall Street"}
Node[4]{name:"WallStreet", title:"Wall Street"} Node[1]{name:"Charlie Sheen"}
2 rows

3.3. Movie Database


Our example graph consists of movies with title and year and actors with a name. Actors have
ACTS_IN relationships to movies, which represents the role they played. This relationship also

CHAPTER 3 17
has a role attribute.
We’ll go with three movies and three actors:
CREATE (matrix1:Movie { title : 'The Matrix', year : '1999-03-31' })
CREATE (matrix2:Movie { title : 'The Matrix Reloaded', year : '2003-05-07' })
CREATE (matrix3:Movie { title : 'The Matrix Revolutions', year : '2003-10-27' })
CREATE (keanu:Actor { name:'Keanu Reeves' })
CREATE (laurence:Actor { name:'Laurence Fishburne' })
CREATE (carrieanne:Actor { name:'Carrie-Anne Moss' })
CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix1)
CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix2)
CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix3)
CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix1)
CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix2)
CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix3)
CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix1)
CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix2)
CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix3)

This gives us the following graph to play with:

Actor Actor Actor

nam e = 'Keanu Reeves' nam e = 'Carrie-Anne Moss' nam e = 'Laurence Fishburne'

ACTS_IN ACTS_IN ACTS_IN ACTS_IN ACTS_IN ACTS_IN ACTS_IN ACTS_IN ACTS_IN


role = 'Neo' role = 'Trinity' role = 'Neo' role = 'Trinity' role = 'Morpheus' role = 'Neo' role = 'Trinity' role = 'Morpheus' role =
'Morpheus'

Movie Movie Movie

t it le = 'The Matrix Reloaded' t it le = 'The Matrix' t it le = 'The Matrix Revolutions'


year = '2003-05-07' year = '1999-03-31' year = '2003-10-27'

Let’s check how many nodes we have now:


MATCH (n)
RETURN "Hello Graph with " + count(*)+ " Nodes!" AS welcome;

Return a single node, by name:


MATCH (movie:Movie { title: 'The Matrix' })
RETURN movie;

Return the title and date of the matrix node:


MATCH (movie:Movie { title: 'The Matrix' })
RETURN [Link], [Link];

Which results in:

[Link] [Link]
"The Matrix" "1999-03-31"
1 row

Show all actors:


MATCH (actor:Actor)
RETURN actor;

CHAPTER 3 18
Return just the name, and order them by name:
MATCH (actor:Actor)
RETURN [Link]
ORDER BY [Link];

Count the actors:


MATCH (actor:Actor)
RETURN count(*);

Get only the actors whose names end with “s”:


MATCH (actor:Actor)
WHERE [Link] =~ ".*s$"
RETURN [Link];

Here’s some exploratory queries for unknown datasets. Don’t do this on live produc%on
databases!
Count nodes:
MATCH (n)
RETURN count(*);

Count relationship types:


MATCH (n)-[r]->()
RETURN type(r), count(*);

type(r) count(*)
"ACTS_IN" 9
1 row

List all nodes and their relationships:


MATCH (n)-[r]->(m)
RETURN n AS FROM , r AS `->`, m AS to;

from -> to
Node[3]{name:"Keanu Reeves"} :ACTS_IN[0]{role:"Neo"} Node[0]{title:"The
Matrix", year:"1999-03-
31"}
Node[3]{name:"Keanu Reeves"} :ACTS_IN[1]{role:"Neo"} Node[1]{title:"The Matrix
Reloaded", year:"2003-05-07"}
Node[3]{name:"Keanu Reeves"} :ACTS_IN[2]{role:"Neo"} Node[2]{title:"The Matrix
Revolutions", year:"2003-10-
27"}
Node[4]{name:"Laurence :ACTS_IN[3]{role:"Morpheus"} Node[0]{title:"The
Fishburne"} Matrix", year:"1999-03-
31"}
Node[4]{name:"Laurence :ACTS_IN[4]{role:"Morpheus"} Node[1]{title:"The Matrix
Fishburne"} Reloaded", year:"2003-05-07"}

CHAPTER 3 19
Node[4]{name:"Laurence :ACTS_IN[5]{role:"Morpheus"} Node[2]{title:"The Matrix
Fishburne"} Revolutions", year:"2003-10-
27"}
Node[5]{name:"Carrie-Anne :ACTS_IN[6]{role:"Trinity"} Node[0]{title:"The
Moss"} Matrix", year:"1999-03-
31"}
Node[5]{name:"Carrie-Anne :ACTS_IN[7]{role:"Trinity"} Node[1]{title:"The Matrix
Moss"} Reloaded", year:"2003-05-07"}
Node[5]{name:"Carrie-Anne :ACTS_IN[8]{role:"Trinity"} Node[2]{title:"The Matrix
Moss"} Revolutions", year:"2003-10-
27"}
9 rows

3.4. Social Movie Database


Our example graph consists of movies with title and year and actors with a name. Actors have
ACTS_IN relationships to movies, which represents the role they played. This relationship also
has a role attribute.
So far, we queried the movie data; now let’s update the graph too.
CREATE (matrix1:Movie { title : 'The Matrix', year : '1999-03-31' })
CREATE (matrix2:Movie { title : 'The Matrix Reloaded', year : '2003-05-07' })
CREATE (matrix3:Movie { title : 'The Matrix Revolutions', year : '2003-10-27' })
CREATE (keanu:Actor { name:'Keanu Reeves' })
CREATE (laurence:Actor { name:'Laurence Fishburne' })
CREATE (carrieanne:Actor { name:'Carrie-Anne Moss' })
CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix1)
CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix2)
CREATE (keanu)-[:ACTS_IN { role : 'Neo' }]->(matrix3)
CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix1)
CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix2)
CREATE (laurence)-[:ACTS_IN { role : 'Morpheus' }]->(matrix3)
CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix1)
CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix2)
CREATE (carrieanne)-[:ACTS_IN { role : 'Trinity' }]->(matrix3)

We will add ourselves, friends and movie ratings.


Here’s how to add a node for yourself and return it, let’s say your name is “Me”:
CREATE (me:User { name: "Me" })
RETURN me;

me
Node[6]{name:"Me"}
1 row
Nodes created: 1
Properties set: 1
Labels added: 1

CHAPTER 3 20
Let’s check if the node is there:
MATCH (me:User { name: "Me" })
RETURN [Link];

Add a movie rating:


MATCH (me:User { name: "Me" }),(movie:Movie { title: "The Matrix" })
CREATE (me)-[:RATED { stars : 5, comment : "I love that movie!" }]->(movie);

Which movies did I rate?


MATCH (me:User { name: "Me" }),(me)-[rating:RATED]->(movie)
RETURN [Link], [Link], [Link];

[Link] [Link] [Link]


"The Matrix" 5 "I love that movie!"
1 row

We need a friend!
CREATE (friend:User { name: "A Friend" })
RETURN friend;

Add our friendship idempotently, so we can re-run the query without adding it several
times. We return the relationship to check that it has not been created several times.
MATCH (me:User { name: "Me" }),(friend:User { name: "A Friend" })
CREATE UNIQUE (me)-[friendship:FRIEND]->(friend)
RETURN friendship;

You can rerun the query, see that it doesn’t change anything the second
time! Let’s update our friendship with a since property:
MATCH (me:User { name: "Me" })-[friendship:FRIEND]->(friend:User { name: "A Friend" })
SET [Link]='forever'
RETURN friendship;

Let’s pretend us being our friend and wanting to see which movies our friends have rated.
MATCH (me:User { name: "A Friend" })-[:FRIEND]-(friend)-[rating:RATED]->(movie)
RETURN [Link], avg([Link]) AS stars, collect([Link]) AS comments, count(*);

[Link] stars comments count(*)


"The Matrix" 5. 0 ["I love that movie!"] 1
1 row

That’s too little data, let’s add some more friends and friendships.
MATCH (me:User { name: "Me" })
FOREACH (i IN range(1,10)| CREATE (friend:User { name: "Friend " + i }),(me)-[:FRIEND]->(friend));

Show all our friends:


MATCH (me:User { name: "Me" })-[r:FRIEND]->(friend)
RETURN type(r) AS friendship, [Link];

CHAPTER 3 21
friendship [Link]
"FRIEND" "A Friend"
"FRIEND" "Friend 1"
"FRIEND" "Friend 2"
"FRIEND" "Friend 3"
"FRIEND" "Friend 4"
"FRIEND" "Friend 5"
"FRIEND" "Friend 6"
"FRIEND" "Friend 7"
"FRIEND" "Friend 8"
"FRIEND" "Friend 9"
"FRIEND" "Friend 10"
10 rows

3.5. Labels, Constraints and Indexes


Labels are a convenient way to group nodes together. They are used to restrict queries, define
constraints and create indexes.
The following will give an example of how to use labels. Let’s start out adding a constraint, in
this case we decided that all Movie node titles should be unique.
CREATE CONSTRAINT ON (movie:Movie) ASSERT [Link] IS UNIQUE

Note that adding the unique constraint will add an index on that property, so we won’t do that
separately. If we drop the constraint, we will have to add an index instead, as needed.
In this case we want an index to speed up finding actors by name in the database:
CREATE INDEX ON :Actor(name)

Indexes can be added at any time. Constraints can be added after a label is already in use, but
that requires that the existing data complies with the constraints. Note that it will take some time
for an index to come online when there’s existing data.
Now, let’s add some data.
CREATE (actor:Actor { name:"Tom Hanks" }),(movie:Movie { title:'Sleepless IN Seattle' }),
(actor)-[:ACTED_IN]->(movie);

Normally you don’t specify indexes when querying for data. They will be used automatically.
This means we can simply look up the Tom Hanks node, and the index will kick in behind the
scenes to boost performance.
MATCH (actor:Actor { name: "Tom Hanks" })
RETURN actor;

Now let’s say we want to add another label for a node. Here’s how to do that:

CHAPTER 3 22
MATCH (actor:Actor { name: "Tom Hanks" })
SET actor :American;

To remove a label from nodes, this is what to do:


MATCH (actor:Actor { name: "Tom Hanks" })
REMOVE actor:American;

3.6. Aggregations
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 sub graphs, or it can be further divided by introducing
key values. These are non-aggregate expressions, that are used to group the values going into the
aggregate functions.
So, if the return statement looks something like this:
RETURN n, count(*)

We have two return expressions — n, and count(*). The first, n, is no aggregate function, and so it
will be the grouping key. The latter, count(*) is an aggregate expression. So the matching
subgraphs will be divided into different buckets, depending on the grouping key. The aggregate
function will then run on these buckets, calculating the aggregate values.
If you want to use aggregations to sort your result set, the aggregation must be included in the RETURN
to be used in your ORDER BY.
The last piece of the puzzle is the DISTINCT keyword. It is used to make all values unique before
running them through an aggregate function.
An example might be helpful. In this case, we are running the query against the following data:

CHAPTER 3 23
Person

nam e = 'A'
property = 13

KNOWS KNOWS KNOWS

Person Person
Person
nam e = 'B' nam e = 'C'
nam e = 'D'
property = 33 property = 44
eyes = 'brown'
eyes = 'blue' eyes = 'blue'

KNOWS KNOWS

Person

nam e = 'D'

Query.
MATCH (me:Person)-->(friend:Person)-->(friend_of_friend:Person)
WHERE [Link] = '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 funcNon, count(DISTINCT friend_of_friend), will only see a friend_of_friend once —
DISTINCT removes the duplicates. The laXer aggregate funcNon, count(friend_of_friend), might
very well see the same friend_of_friend mulNple Nmes. 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 10.5. Graph

CHAPTER 3 24
Person

nam e = 'A'
property = 13

KNOWS KNOWS KNOWS

Person Person
Person
nam e = 'B' nam e = 'C'
nam e = 'D'
property = 33 property = 44
eyes = 'brown'
eyes = 'blue' eyes = 'blue'

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(<identifier>), which counts the number
of non-NULL values in <identifier>.

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[1]{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

CHAPTER 3 25
Count entities
Instead of counting the number of results with count(*), it might be more expressive to include the
name of the identifier you care about.
Query.
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(<identifier>).
Query.
MATCH (n:Person)
RETURN count([Link])

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

Result
count([Link])
3
1 row

sum
The sum aggregation function simply sums all the numeric values it encounters. NULLs are silently
dropped.
Query.
MATCH (n:Person)
RETURN sum([Link])
This returns the sum of all the values in the property property.

Result
sum([Link])
90
1 row

avg
avg calculates the average of a numeric column.

Query.
CHAPTER 3 26
MATCH (n:Person)
RETURN avg([Link])

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

Result
avg([Link])
30. 0
1 row

max
max find the largest value in a numeric column.

Query.
MATCH (n:Person)
RETURN max([Link])

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


Result
max([Link])
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([Link])

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

Result
min([Link])
13
1 row

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

Query.
MATCH (n:Person)
RETURN collect([Link])

Returns a single row, with all the values collected.

Result

CHAPTER 3 27
collect([Link])
[13, 33, 44]
1 row

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 [Link])

Returns the number of eye colors.

Result
count(distinct [Link])
2
1 row

4. Data Modeling Examples


4.1. Basic friend finding based on social neighborhood
Imagine an example graph like the following one:

nam e = 'Joe'

knows

knows nam e = 'Sara'

knows knows

nam e = 'Bill' knows nam e = 'Jill'

knows knows

nam e = 'Derrick' nam e = 'Ian'

CHAPTER 3 28
To find out the friends of Joe’s friends that are not already his friends, the query looks like this:
Query.
MATCH (joe { name: 'Joe' })-[:knows*2..2]-(friend_of_friend)
WHERE NOT (joe)-[:knows]-(friend_of_friend)
RETURN friend_of_friend.name, COUNT(*)
ORDER BY COUNT(*) DESC , friend_of_friend.name

This returns a list of friends-of-friends ordered by the number of connections to them, and
secondly by their name.

Result
friend_of_friend.name COUNT(*)
"Ian" 2
"Derrick" 1
"Jill" 1
3 rows

4.2. Find people based on similar favorites

To find out the possible new friends based on them liking similar things as the asking person,
use a query like this:
Query.
MATCH (me { name: 'Joe' })-[:favorite]->(stuff)<-[:favorite]-(person)
WHERE NOT (me)-[:friend]-(person)
RETURN [Link], count(stuff)
ORDER BY count(stuff) DESC

The list of possible friends ranked by them liking similar stuff that are not yet friends is returned.

Result
[Link] count(stuff)
"Derrick" 2
"Jill" 1

CHAPTER 3 29
2 rows

4.3. Find people based on mutual friends and groups

In this scenario, the problem is to determine mutual friends and groups, if any, between persons. If
no mutual groups or friends are found, there should be a 0 returned.
Query.
MATCH (me { name: 'Joe' }),(other)
WHERE [Link] IN ['Jill', 'Bob']
OPTIONAL MATCH pGroups=(me)-[:member_of_group]->(mg)<-[:member_of_group]-(other)
OPTIONAL MATCH pMutualFriends=(me)-[:knows]->(mf)<-[:knows]-(other)
RETURN [Link] AS name, count(DISTINCT pGroups) AS mutualGroups,
count(DISTINCT pMutualFriends) AS mutualFriends
ORDER BY mutualFriends DESC

The question we are asking is — how many unique paths are there between me and Jill, the
paths being common group memberships, and common friends. If the paths are mandatory, no
results will be returned if me and Bob lack any common friends, and we don’t want that. To
make a path optional, you have to make at least one of it’s relationships optional. That makes
the whole path optional.

Result
name mutualGroups mutualFriends
"Jill" 1 1
"Bob" 1 0
2 rows

CHAPTER 3 30
4.4. Example of TV Shows
This example show how TV Shows with Seasons, Episodes, Characters, Actors, Users and
Reviews can be modeled in a graph database.

Data Model
Let’s start out with an entity-relationship model of the domain at hand:

TV Show

has

Season

has

User Episode Actor

wrote has featured played

Review Character

To implement this in Neo4j we’ll use the following relationship types:

Relationship Type Description


HAS_SEASON Connects a show with its seasons.
HAS_EPISODE Connects a season with its episodes.
FEATURED_CHARACTER Connects an episode with its characters.
PLAYED_CHARACTER Connects actors with characters. Note that an
actor can play multiple characters in an episode,
and that the same character can be played by
multiple actors as well.
HAS_REVIEW Connects an episode with its reviews.
WROTE_REVIEW Connects users with reviews they contributed.

Sample Data
Let’s create some data and see how the domain plays out in practice:

CHAPTER 3 31
CREATE (himym:TVShow { name: "How I Met Your Mother"

CREATE (himym_s1:Season { name: "HIMYM Season 1" })


CREATE (himym_s1_e1:Episode { name: "Pilot" })
CREATE (ted:Character { name: "Ted Mosby" })
CREATE (joshRadnor:Actor { name: "Josh Radnor" })
CREATE UNIQUE (joshRadnor)-[:PLAYED_CHARACTER]->(ted)
CREATE UNIQUE (himym)-[:HAS_SEASON]->(himym_s1)
CREATE UNIQUE (himym_s1)-[:HAS_EPISODE]->(himym_s1_e1)
CREATE UNIQUE (himym_s1_e1)-[:FEATURED_CHARACTER]->(ted)
CREATE (himym_s1_e1_review1 { title: "Meet Me At The Bar In 15 Minutes & Suit Up",
content: "It was awesome" })
CREATE (wakenPayne:User { name: "WakenPayne" })
CREATE (wakenPayne)-[:WROTE_REVIEW]->(himym_s1_e1_review1)<-[:HAS_REVIEW]-(himym_s1_e1)

This is how the data looks in the database:

TVShow

nam e = 'How I Met Your Mother'

HAS_SEASON

Season

nam e = 'HIMYM Season 1'

HAS_EPISODE

Actor Episode User

nam e = 'Josh Radnor' nam e = 'Pilot ' nam e = 'WakenPayne'

PLAYED_CHARACTER FEATURED_CHARACTER HAS_REVIEW WROTE_REVIEW

Character
t it le = 'Meet Me At The Bar In 15 Minutes & Suit Up'
nam e = 'Ted Mosby' content = 'It was awesom e'

Note that even though we could have modeled the reviews as relationships with title and content
properties on them, we made them nodes instead. We gain a lot of flexibility in this way, for
example if we want to connect comments to each review.
Now let’s add more data:

CHAPTER 3 32
MATCH (himym:TVShow { name: "How I Met Your Mother" }),(himym_s1:Season),
(himym_s1_e1:Episode { name: "Pilot" }),
(himym)-[:HAS_SEASON]->(himym_s1)-[:HAS_EPISODE]->(himym_s1_e1)
CREATE (marshall:Character { name: "Marshall Eriksen" })
CREATE (robin:Character { name: "Robin Scherbatsky" })
CREATE (barney:Character { name: "Barney Stinson" })
CREATE (lily:Character { name: "Lily Aldrin" })
CREATE (jasonSegel:Actor { name: "Jason Segel" })
CREATE (cobieSmulders:Actor { name: "Cobie Smulders" })
CREATE (neilPatrickHarris:Actor { name: "Neil Patrick Harris" })
CREATE (alysonHannigan:Actor { name: "Alyson Hannigan" })
CREATE UNIQUE (jasonSegel)-[:PLAYED_CHARACTER]->(marshall)
CREATE UNIQUE (cobieSmulders)-[:PLAYED_CHARACTER]->(robin)
CREATE UNIQUE (neilPatrickHarris)-[:PLAYED_CHARACTER]->(barney)
CREATE UNIQUE (alysonHannigan)-[:PLAYED_CHARACTER]->(lily)
CREATE UNIQUE (himym_s1_e1)-[:FEATURED_CHARACTER]->(marshall)
CREATE UNIQUE (himym_s1_e1)-[:FEATURED_CHARACTER]->(robin)
CREATE UNIQUE (himym_s1_e1)-[:FEATURED_CHARACTER]->(barney)
CREATE UNIQUE (himym_s1_e1)-[:FEATURED_CHARACTER]->(lily)
CREATE (himym_s1_e1_review2 { title: "What a great pilot for a show :)",
content: "The humour is great." })
CREATE (atlasredux:User { name: "atlasredux" })
CREATE (atlasredux)-[:WROTE_REVIEW]->(himym_s1_e1_review2)<-[:HAS_REVIEW]-(himym_s1_e1)

Information for a show


For a particular TV show, show all the seasons and all the episodes and all the reviews and all the
cast members from that show, that is all of the information connected to that TV show.
MATCH (tvShow:TVShow)-[:HAS_SEASON]->(season)-[:HAS_EPISODE]->(episode)
WHERE [Link] = "How I Met Your Mother"
RETURN [Link], [Link]

[Link] [Link]
"HIMYM Season 1" "Pilot"
1 row

We could also grab the reviews if there are any by slightly tweaking the query:
MATCH (tvShow:TVShow)-[:HAS_SEASON]->(season)-[:HAS_EPISODE]->(episode)
WHERE [Link] = "How I Met Your Mother"
WITH season, episode
OPTIONAL MATCH (episode)-[:HAS_REVIEW]->(review)
RETURN [Link], [Link], review

[Link] [Link] review


"HIMYM Season 1" "Pilot" Node[5]{title:"Meet Me At
The Bar In 15 Minutes & Suit
Up", content:"It was
awesome"}
"HIMYM Season 1" "Pilot" Node[15]{title:"What a
great pilot for a show
:)",
content:"The humour is great.
"}
2 rows

CHAPTER 3 33
Now let’s list the characters featured in a show. Note that in this query we only put identifiers on
the nodes we actually use later on. The other nodes of the path pattern are designated by ().
MATCH (tvShow:TVShow)-[:HAS_SEASON]->()-[:HAS_EPISODE]->()-[:FEATURED_CHARACTER]->(character)
WHERE [Link] = "How I Met Your
Mother" RETURN DISTINCT [Link]

[Link]
"Ted Mosby"
"Marshall Eriksen"
"Robin Scherbatsky"
"Barney Stinson"
"Lily Aldrin"
5 rows

Now let’s look at how to get all cast members of a show.


MATCH
(tvShow:TVShow)-[:HAS_SEASON]->()-[:HAS_EPISODE]->(episode)-[:FEATURED_CHARACTER]->()<-[:PLAYED_CHARACTER]-(actor)
WHERE [Link] = "How I Met Your Mother"
RETURN DISTINCT [Link]

[Link]
"Josh Radnor"
"Jason Segel"
"Cobie Smulders"
"Neil Patrick Harris"
"Alyson Hannigan"
5 rows

Information for an actor


First let’s add another TV show that Josh Radnor appeared in:
CREATE (er:TVShow { name: "ER" })
CREATE (er_s9:Season { name: "ER S7" })
CREATE (er_s9_e17:Episode { name: "Peter's Progress" })
CREATE (tedMosby:Character { name: "The Advocate " })
CREATE UNIQUE (er)-[:HAS_SEASON]->(er_s9)
CREATE UNIQUE (er_s9)-[:HAS_EPISODE]->(er_s9_e17)
WITH er_s9_e17
MATCH (actor:Actor),(episode:Episode)
WHERE [Link] = "Josh Radnor" AND [Link] = "Peter's Progress"
WITH actor, episode
CREATE (keith:Character { name: "Keith" })
CREATE UNIQUE (actor)-[:PLAYED_CHARACTER]->(keith)
CREATE UNIQUE (episode)-[:FEATURED_CHARACTER]->(keith)

And now we’ll create a query to find the episodes that he has appeared in:
MATCH (actor:Actor)-[:PLAYED_CHARACTER]->(character)<-[:FEATURED_CHARACTER]-(episode)
WHERE [Link] = "Josh Radnor"
RETURN [Link] AS Episode, [Link] AS Character

CHAPTER 3 34
Episode Character
"Pilot" "Ted Mosby"
"Peter's Progress" "Keith"
2 rows

Now let’s go for a similar query, but add the season and show to it as well.
MATCH (actor:Actor)-[:PLAYED_CHARACTER]->(character)<-[:FEATURED_CHARACTER]-(episode),
(episode)<-[:HAS_EPISODE]-(season)<-[:HAS_SEASON]-(tvshow)
WHERE [Link] = "Josh Radnor"
RETURN [Link] AS Show, [Link] AS Season, [Link] AS Episode,
[Link] AS Character

Show Season Episode Character


"How I Met Your "HIMYM Season 1" "Pilot" "Ted Mosby"
Mother"
"ER" "ER S7" "Peter's Progress" "Keith"
2 rows

CHAPTER 3 35
CHAPTER 3 36

You might also like