Skip to content
andreasronge edited this page Sep 24, 2012 · 26 revisions

“Cypher” is a declarative graph query language that allows for expressive and efficient querying of the graph store without having to write traversals through the graph structure in code.

Cypher

The cypher language is described here: Neo4j Cypher Documentation

The neo4j-cypher gem can be used both from neo4j.rb (see Neo4j::Core-Cypher) and from the Neo4j rest API, see neography

Generating a Cypher String from a DSL

The following DSL:

Neo4j::Cypher.query do
  node(3) > :r > :x
end.to_s

will generate the following string "START v1=node(3) MATCH v2 = (v1)-[:`r`]->(x) RETURN v2"

As shown above the DSL can automatically generate new variables. In order to know which values are returned there is a return_names available.

Example:

Neo4j::Cypher.query do
    node(3) > :r > :x
end.return_names

It will return the following array: [:v2]

Notice that both neography and neo4j-core includes methods which also executes the query.

Using Neo4j::Cypher with Neography

require 'neography'
require 'neo4j-cypher'
require 'neo4j-cypher/neography' #adds a Neography::Rest#execute_cypher method

@neo = Neography::Rest.new
@neo.execute_cypher(n) { |me| me > ':friends' > node > ':friends' > node(:foaf)[:name].ret }['data']

See this for a complete example.

Start Nodes

Every query describes a pattern, and in that pattern one can have multiple start points. A start point is a relationship or a node that form the starting points for a pattern match. You can either introduce start points by id, or by index lookups. Note that trying to use an index that doesn’t exist will throw an exception.

Node By Id

Binding a node as a start point is done with the node(*) method.

node(3)

Will generate START v0=node(3) RETURN v0. Notice that the last value evaluated will be the return value (when possible).

Change Variable Name

There is a #as method which will change the name of the columns. The #as method is available on nodes, relationships, functions (like count) and paths.

Example:

node(3).as(:x)

Will generate START x = node(3) RETURN x

Variables

Nodes and Relationships can also be used in WHERE and MATCH clauses. To express that you want any node use the node or rel without any argument. Example:

  node(3) <=> node

which is same as: START v1=node(1) MATCH v3 = (v1)--(v2) RETURN v3 The spaceship operator <=> means match any outgoing or incoming relationship.

The #as method can be used if you need to specify the name of the variable. Example:

  node(3) <=> node.as(:friends)

Which will instead generate the following string START v1=node(1) MATCH v2 = (v1)--(friend) RETURN v2

A shorter way of doing this is using ruby symbols:

  node(3) <=> node(:friends)

An even short way of doing this is using ruby symbols.

  node(3) <=> :friends

Relationships

You can start from a relationship just like you do with nodes.

Neo4j.query{ rel(3) }
# same as "START r0=relationship(3) RETURN r0"

Just like nodes you can use symbols to represent a relationship variable.

Neo4j.query{node(3) > :r > 'bla'; :x}
# same as "START n0=node(3) MATCH (n0)-[:r]->(bla) RETURN x"

If a relationship is a String it will be used in the match clause as is.

Neo4j.query{node(3) > ':friend|knows' > 'bla'; :x}
# same as "START n0=node(3) MATCH (n0)-[:friend|knows]->(bla) RETURN x"

You can also use relationship as variables. Let say you want to return all the relationships instead from the query above. This can be done like this:

Neo4j.query{node(3) > (r=rel(':friend|knows')) > 'bla'; r}
# same as "START n0=node(3) MATCH (n0)-[r:friend|knows]->(bla) RETURN r"

Index Lookup

Since you can inject your own nodes and relationship as argument to the Cypher DSL there is less needed to use the lookup and query methods.

Example of lookup

Neo4j.query { lookup(Person, "desc", "A")
# same as "START n0=node:person_fulltext(desc="A") RETURN n0"

Example of query

Neo4j.query { query(Person, "name:A", :fulltext) }
# same as "START n0=node:person_fulltext(name:A) RETURN n0])"

Example of using query argument:

# Notice that the neo4j-core and neo4j find method behaves differently.
# For Neo4j::Rails::Model.find the first method is not needed since it returns one node
Neo4j.query(Person.find("name: A").first) { |n| n}

The lookup and query methods knows where the lucene index is stored.

Match

Pattern matching is one of the pillars of Cypher. The pattern is used to describe the shape of the data that we are looking for. Cypher will then try to find patterns in the graph — these are called matching sub graphs.

In Neo4j.rb there are several methods to specify a match pattern by using the <, >, >>, <<, <=> and outgoing and incoming methods.

The <, > and - operators is used when you want to specify relationship type(s) between two nodes.

Example: node > ':knows|friend' > other_node

The <<, <=> and outgoing and incoming or used when you don't care what type of relationship the has.

Example: node >> other_node

Related nodes

The method <=> will generate a pattern match without regard to type or direction.

Example:

Neo4j.query{node(3, 4) <=> :x; node(:x)[:desc] =~ /hej/; :x}
# Same as "START n0=node(3,4) MATCH (n0)--(x) WHERE x.desc =~ /hej/ RETURN x"

Outgoing or Incoming of a Type

If you don't want to specify the direction of the relationship but only the type of relationship you can use the - operator:

Neo4j.query{node(3) - ':knows|friends' - :foo; :foo}
# Same as "START n0=node(3) MATCH (n0)-[:knows|friends]-(foo) RETURN foo"

Outgoing/Incoming Relationships

You can specify the direction of relationship to match.

Example: match all outgoing relationship of any type

Neo4j.query{node(3, 4) >> :x;  :x}
# Same as "START n0=node(3,4) MATCH (n0)-->(x) RETURN x"

By using the outgoing and incoming method you can use the declared has_n/has_one relationship types. Example, return friends of friends

Neo4j.query(Person.find(...)){|person| person.outgoing(Person.friends).outgoing(Person.friends)}
# Same as "START n0=node(some_id) MATCH (n0)-[:`knows`]->(v0),(v0)-[:`knows`]->(v1) RETURN v1"

Paths

A path specified with for example << has a number of useful methods. For example, we can return the length of the path like this:

Neo4j.query { p = node(3) >> :b; p.length }

Available methods for paths, see MatchRelRight

Binding Matching Nodes to Variables

It is often useful to assign a variable to a path (as seen above), relationships or nodes. By doing that you can use the variable as a condition in the WHERE clause or in the RETURN clause.

Example: return all outgoing nodes with property name equal 'andreas'

Neo4j.query { node(3) >> (x = node); x[:name] == 'andreas'; x }

Where

You specify where clauses by using the ==, !=, not < comparable methods (Comparable) on properties (Property) or the is_a?, property?, exist? on node or relationship variables ([Neo4j::Core::Cypher::Variable|http://rdoc.info/github/andreasronge/neo4j-core/Neo4j/Core/Cypher/Variable])

Example:

Neo4j.query {  n=node(3, 4); n[:desc] == "hej"; n }
# same as START n0=node(3,4) WHERE n0.desc = "hej" RETURN n0

Example for optinal properties

Neo4j.query {  n=node(3, 4); n[:desc?] == "hej"; n }
# same as START n0=node(3,4) WHERE n0.desc? = "hej" RETURN n0

Checking if an property exists:

Neo4j.query {  n=node(3, 1); n.property?(:belt); n }
# same as START n0=node(3,1) WHERE has(n0.belt) RETURN n0

Available methods for variables see Property

none? single? any?

The none?, single? any? methods can be found on both path variable or property array.

Example:

Neo4j.query {  p=node(3)>'*1..3'>:b; p.nodes.none? { |x| x[:age] == 25 }}
# same as START n0=node(3) MATCH m2 = (n0)-[*1..3]->(b) WHERE none(x in nodes(m2) WHERE x.age = 25) RETURN m2

Boolean AND, OR operators

The ruby &, | and ! (only for Ruby 1.9) and not can be used to make compound where statements.

Example:

Neo4j.query { n=node(3, 1); where((n[:age] < 30) & ((n[:name] == 'foo') | (n[:size] > n[:age]))); ret n }
# same as START n0=node(3,1) WHERE (n0.age < 30) and ((n0.name = "foo") or (n0.size > n0.age)) RETURN n0

(use where method below because it is easier to read)

Regular Expression

It uses normal Ruby syntax.

Example: node(:x)[:desc] =~ /hej/

Mixing Match and Where

The DSL will take care of what is a match and what is a where clause. That means you can write queries like this:

node(1) > (rel(:knows)[:since] > 1994) > (node(:other)[:name] == 'foo'); :other

Which is same as :

START n0=node(1) MATCH (n0)-[v1:`knows`]->(other) WHERE v1.since > 1994 and other.name = "foo" RETURN other

Match with WHERE and WHERE_NOT

Cypher allows you to have a match in a where clause, example:

START n0=node(1),interest=node(7) MATCH (n0)<--(person) WHERE (person)-->(interest) RETURN person

Use where and where_not on nodes to achieve the same thing, example

node(1) << node(:person).where{|p| p >> node(7).as(:interest)}; :person

Return

The last statement will be used as the cypher return clause.

Example:

Neo4j.query { a=node(3); b = node(2); ret a[:age], b[:age], (a[:age] - b[:age]).abs}
# same as START n0=node(3),n1=node(2) RETURN n0.age,n1.age,abs(n0.age - n1.age)
# instead or ret you can simply return an array

Sorting

Sorting can be done using the asc and desc methods.

Neo4j.query { n=node(3, 1, 2); ret(n, n[:name]).asc(n[:name], n[:age]) }
# same as START n0=node(3,1,2) RETURN n0,n0.name ORDER BY n0.name, n0.age
# instead or ret you can simply return an array

Examples

Example 5.2. Basic Friend finding based on social neighborhood

See http://docs.neo4j.org/chunked/snapshot/cypher-cookbook-friend-finding.html Let say we want to express the following Cypher Query:

START joe=node(some node id)
MATCH joe-[:knows]->friend-[:knows]->friend_of_friend, joe-[r?:knows]->friend_of_friend
WHERE r IS NULL
RETURN friend_of_friend.name, COUNT(*)
ORDER BY COUNT(*) DESC, friend_of_friend.name

This can be done like this:

Neo4j.query(joe_node) do |joe|
  friends_of_friends = node(:friends_of_friends)
  joe > ':knows' > node(:friend) > ':knows' > friends_of_friends
  r = rel('r?:knows')
  joe > r > friends_of_friends
  r.exist?
  ret(friends_of_friends[:name], count).desc(count).asc(friends_of_friends[:name])
end

Or like this using the outgoing instead of the > operator

Neo4j.query(joe_node) do |joe|
 friends_of_friends = joe.outgoing(:knows).outgoing(:knows)
 r = rel('r?:knows').as(:r)
 joe > r > friends_of_friends
 r.exist?
 ret(friends_of_friends[:name], count).desc(count).asc(friends_of_friends[:name])
end

Example 5.4. Find people based on similar favorites

Neo4j.query(me_node) do |me|
  me.where_not { |m| m - ':friend' - :person } > ':favorite' > :stuff < ':favorite' < :person
  ret(node(:person)[:name], count(:stuff).desc(count(:stuff)))
end

Is generates the following cypher string:

START n0=node(42) MATCH (n0)-[:favorite]->(stuff)<-[:favorite]-(person) WHERE not((n0)-[:friend]-(person)) RETURN person.name,count(stuff) ORDER BY count(stuff) DESC
Clone this wiki locally