QueryingDifferences in Cypher implementations

Differences in Cypher implementations

Memgraph implements the openCypher query language and aims to be as close as possible to the most commonly used openCypher implementations. Still, there are some differences in Memgraph Cypher implementation that enhance the user experience.

Difference from Neo4j’s Cypher implementation

The openCypher initiative stems from Neo4j’s Cypher query language. Following is a list of the most important differences between Neo4j’s and Memgraph’s Cypher implementation is for users who are already familiar with Neo4j.

Indexes and constraints

In Memgraph, indexes are not created in advance and creating constraints does not imply index creation. Memgraph supports label-property and label node indexes, node property existence and uniqueness constraints.

Memgraph accepts both its native ON :Label(property) / ASSERT syntax and the Neo4j-compatible FOR (...) ON (...) / FOR (...) REQUIRE ... syntax, so existing Neo4j code that creates indexes or constraints can run unchanged.

Indexes

The following Neo4j-style queries all work in Memgraph:

CREATE INDEX FOR (n:Person) ON (n.surname);
CREATE INDEX person_surname FOR (n:Person) ON (n.surname);
CREATE INDEX FOR (n:Person) ON (n.age, n.country);
CREATE INDEX FOR ()-[r:KNOWS]-() ON (r.since);

The native Memgraph syntax remains supported as well:

CREATE INDEX ON :Person(surname);
CREATE INDEX ON :Person;
CREATE EDGE INDEX ON :KNOWS(since);

You can instruct the planner to use specific index(es) in Memgraph by using the syntax below:

USING INDEX :Label1, :Label2 ...;
USING INDEX :Label(property) ...;

Besides index hinting, the ANALYZE GRAPH feature can also be applied to optimize performance.

Constraints

Existence, uniqueness, and type constraints can be created with either syntax:

-- existence
CREATE CONSTRAINT FOR (n:Author) REQUIRE n.name IS NOT NULL;
CREATE CONSTRAINT ON (n:Author) ASSERT EXISTS (n.name);
 
-- uniqueness (single and composite)
CREATE CONSTRAINT FOR (n:Book) REQUIRE n.isbn IS UNIQUE;
CREATE CONSTRAINT FOR (n:Book) REQUIRE (n.title, n.year) IS UNIQUE;
CREATE CONSTRAINT ON (n:Book) ASSERT n.isbn IS UNIQUE;
 
-- type
CREATE CONSTRAINT FOR (n:Movie) REQUIRE n.title IS :: STRING;
CREATE CONSTRAINT ON (n:Movie) ASSERT n.title IS TYPED STRING;

Constraint names in the Neo4j-style syntax are parsed but not stored — Memgraph does not have a named index/constraint system, so a WARNING notification is emitted to the client when a name is provided. Drops therefore must use the DROP CONSTRAINT ON (...) ASSERT ... form rather than DROP CONSTRAINT <name>.

Relationship uniqueness, existence, and type constraints are not supported and will raise a SemanticException.

Shortest path

In Neo4j, to find the shortest possible path between two nodes, you would use the shortestPath algorithm:

MATCH p=shortestPath(
(:Person {name:"Keanu Reeves"})-[*]-(:Person {name:"Tom Hanks"})
)
RETURN p

Memgraph offers fast deep path traversals as built-in graph algorithms, including BFS, DFS, WSP, ASP, and KSP algorithms. That is a bit different from the shortestPath and allShortestPaths functions you might be used to, but with such algorithms being built in, Memgraph offers fast traversals. Here is an example of how you would rewrite the above query to work in Memgraph:

MATCH p=(:Person {name:"Keanu Reeves"})-[*BFS]-(:Person {name:"Tom Hanks"})
RETURN p

K shortest paths

In Neo4j, to find K shortest paths between two nodes, you would use the SHORTEST algorithm with a number:

MATCH p = SHORTEST 3 (start:A)-[:E]->(end:B) 
RETURN p;

In Memgraph, you need to first match the source and target nodes, then use the *KSHORTEST syntax with a limit:

MATCH (start:A), (end:B) 
WITH start, end 
MATCH p=(start)-[:E *KSHORTEST | 3]->(end) 
RETURN p;

Note that Memgraph requires both source and target nodes to be matched first using a WITH clause before applying the K-shortest paths algorithm.

NOT label expression

In Neo4j, you can use the NOT label expression (!):

MATCH (:Person {name:'Tom Hanks'})-[r:!ACTED_IN]->(m:Movie)
Return type(r) AS type, m.title AS movies

In Memgraph, such a construct is not supported, but there is still a workaround:

MATCH (p:Person {name:'Tom Hanks'})-[r]->(m:Movie)
WHERE type(r) != "ACTED_IN"
RETURN type(r) AS type, m.title AS movies;

Search for patterns of a fixed length

In Neo4j, to search for patterns of a fixed length, you would use the following construct:

MATCH (tom:Person {name:'Tom Hanks'})--{2}(colleagues:Person)
RETURN DISTINCT colleagues.name AS name, colleagues.born AS bornIn
ORDER BY bornIn
LIMIT 5

Memgraph does not support such a construct, but since it has built-in traversals, you can achieve the same with the depth-first search (DFS) algorithm:

MATCH (tom:Person {name:'Tom Hanks'})-[*2]-(colleagues:Person)
RETURN DISTINCT colleagues.name AS name, colleagues.born AS bornIn
ORDER BY bornIn
LIMIT 5

Similarly, to match a graph for patterns of a variable length, you would run the following query in Neo4j:

MATCH (p:Person {name:'Tom Hanks'})--{1,4}(colleagues:Person)
RETURN DISTINCT colleagues.name AS name, colleagues.born AS bornIn
ORDER BY bornIn, name
LIMIT 5

In Memgraph, again use DFS:

MATCH (p:Person {name:'Tom Hanks'})-[*1..4]-(colleagues:Person)
RETURN DISTINCT colleagues.name AS name, colleagues.born AS bornIn
ORDER BY bornIn, name
LIMIT 5

Unsupported constructs

COUNT subqueries

Such a construct is not supported in Memgraph, but you can use count() aggregation function to count the number of non-null values returned by the expression.

COLLECT subqueries

Such a construct is not supported in Memgraph, but you can use collect() aggregation function to return a single aggregated list from provided values.

Patterns in expressions

Patterns in expressions are supported in Memgraph in particular functions, like exists(pattern). Memgraph also supports filtering based on patterns, like MATCH (n) WHERE NOT (n)-->(). In other cases, Memgraph does not yet support patterns in functions, e.g. size((n)-->()). Most of the time, the same functionalities can be expressed differently in Memgraph using OPTIONAL expansions, function calls, etc.

Unsupported expressions

Cypher expressions

  • Numerical:
    • An octal INTEGER literal (starting with 0o): 0o1372, 0o5671
    • A FLOAT literal: Inf, Infinity, NaN

Conditional expressions (CASE)

  • More than one value after WHEN operator:
    MATCH (n:Person)
    RETURN
    CASE n.eyes
      WHEN 'blue'  THEN 1
      WHEN 'brown', 'hazel' THEN 2
      ELSE 3
    END AS result, n.eyes
    Here is a workaround in Memgraph:
    MATCH (n:Person)
    RETURN
    CASE
    WHEN n.eyes='blue' THEN 1
    WHEN n.eyes='brown' OR n.eyes='hazel' THEN 2
    ELSE 3
    END AS result, n.eyes;
    -> Track progress on GitHub and add a comment if you require such a feature.

Type predicate expressions

The following expression is not supported in Memgraph:

UNWIND [42, true, 'abc'] AS val
RETURN val, val IS :: INTEGER AS isInteger

Still, you can check the value type with the valueType() scalar function, which returns the value type of the object in textual format:

UNWIND [42, true, 'abc'] AS val
RETURN val, valueType(val) = "INTEGER"

Unsupported functions

Functions for converting data values:

  • toBooleanList()
  • toBooleanOrNull()
  • toFloatList()
  • toFloatOrNull()
  • toIntegerList()
  • toIntegerOrNull()
  • toStringList()

Predicate functions:

  • exists(n.property) - can be expressed using n.property IS NOT NULL
  • isEmpty()

Scalar functions:

  • elementId() - id() can be used instead
  • nullIf()

Aggregating functions:

  • percentileCont(), percentileDisc()
  • stDev(), stDevP()

Mathematical functions:

  • isNan()
  • cot()
  • degrees()
  • haversin()
  • radians()

String functions:

  • normalize()

Datetime functions:

  • time()

Memgraph’s Cypher extension

Besides implementing openCypher, Memgraph created various language extensions to provide an enhanced user experience. Here are some of the improvements:

For all other unsupported constructs that you require, please open an issue on our GitHub repository.