Filters and projections

After learning how to write a basic MATCH and RETURN Cypher® query in the Querying tutorial, you can now add filters and change the projection to only return some fields, as well as work with parameters.

This tutorial shows how to achieve the resulting query when using the Movies Dataset:

MATCH(m:Movie)
WHERE (m.title = "The Matrix" AND m.released < 2000)
RETURN m.title, m.tagline, m.released

This query should return all movies with the title "The Matrix" released before the year of 2000. It should return only the movie "The Matrix", released in 1999.

Filtering

Adding filters to a MATCH clause has a similar process to how you add the RETURN clause. Here is how you should proceed:

  1. Use the method .where:

    const movieNode = new Cypher.Node()
    const clause = new Cypher.Match(new Cypher.Pattern(movieNode, { labels: ["Movie"] }))
        .where()
        .return(movieNode);
  2. Then, add a condition to the filter:

    m.title = "The Matrix"
  3. Use the function Cypher.eq() to add the property title of the movieNode variable and compare them with an equality operator (=):

    movieNode.property("title")
  4. Use the Cypher.Param class to add the string "The Matrix" as a parameter. It will replace the value with a suitable placeholder:

    new Cypher.Param("The Matrix")

    Though it is possible to inject the string value into the query, it is good practice to use Parameters.

  5. Finally, your MATCH clause should now look like this:

    const clause = new Cypher.Match(new Cypher.Pattern(movieNode, { labels: ["Movie"] }))
        .where(Cypher.eq(movieNode.property("title"), new Cypher.Param("The Matrix")))
        .return(movieNode);
  6. Run the script again (node main.js). You should now get the following Cypher:

    MATCH (this0:Movie)
    WHERE this0.title = $param0
    RETURN this0

    That means Movie is now filtered by its title to a param, but the param itself is missing from the output. To fix that, you need to modify the build process at the end of the script:

    const { cypher, params } = match.build();
    console.log(cypher);
    console.log(params);

    Executing this script prompts the following Cypher query and its parameters:

    MATCH (this0:Movie)
    WHERE this0.title = $param0
    RETURN this0
    { param0: 'The Matrix' }

AND filtering

Here is how to add an AND filter to your query:

  1. Use the method .and after .where, with a new Cypher.eq predicate:

        .where(Cypher.eq(movieNode.property("title"), new Cypher.Param("The Matrix")))
        .and(Cypher.lt(movieNode.property("released"), new Cypher.Param(2000)))

    This takes the same parameters as .where and ensures the proper concatenation of the filters with AND.

    In this case, the function Cypher.lt will add the operator < (lower than) and, like before, the comparison happens between a property of movieNode and a param.

  2. Run the script again. You should get these results:

    MATCH (this0:Movie)
    WHERE (this0.title = $param0 AND this0.released < $param1)
    RETURN this0
    { param0: 'The Matrix', param1: 1999 }

    The AND operation automatically adds parentheses to ensure operation precedence. This is an important feature when dealing with complex and nested filters. See more information about Advanced filtering.

Projection

Returning the full node is usually not necessary, but you can do that by explicitly setting a projection in RETURN. For example:

RETURN m.title, m.tagline, m.released

To define these columns, you can pass the variables into the .return method:

.return(movieNode.property("title"), movieNode.property("tagline"), movieNode.property("year"));

The returned Cypher now should have the explicit projection:

MATCH (this0:Movie)
WHERE (this0.title = $param0 AND this0.released < $param1)
RETURN this0.title, this0.tagline, this0.released

Reusing variables

The current query is filtering and returning the properties id and title of the Movie nodes. However, relying on the name of the properties might make maintenance unecessarily more difficult.

To avoid that scenario, make sure both filters and projections use the same property variable (in this case, movieNode.):

const titleProp = movieNode.property("title");
const yearProp = movieNode.property("released");
const taglineProp = movieNode.property("tagline");

const clause = new Cypher.Match(movieNode)
    .where(Cypher.eq(titleProp, new Cypher.Param("The Matrix")))
    .and(Cypher.lt(yearProp, new Cypher.Param(2000)))
    .return(titleProp, taglineProp, yearProp);

That should keep different parts of the query in sync and also make the clause itself shorter.

Params can also be assigned to a variable and be reused. This can be particularly useful when having multiple filters over the same parameter. See an example in Relationships and advanced filtering.

Conclusion

After going through all the steps previously described, your script should look like this:

import Cypher from "@neo4j/cypher-builder";

const movieNode = new Cypher.Node({
    labels: ["Movie"],
});

const titleProp = movieNode.property("title");
const yearProp = movieNode.property("released");
const taglineProp = movieNode.property("tagline");

const clause = new Cypher.Match(new Cypher.Pattern(movieNode, { labels: ["Movie"] }))
    .where(Cypher.eq(titleProp, new Cypher.Param("The Matrix")))
    .and(Cypher.lt(yearProp, new Cypher.Param(2000)))
    .return(titleProp, taglineProp, yearProp);

const { cypher, params } = clause.build();
console.log(cypher);
console.log(params);

And its execution should show the following query:

MATCH (this0:Movie)
WHERE (this0.title = $param0 AND this0.released < $param1)
RETURN this0.title, this0.tagline, this0.released
{ param0: 'The Matrix', param1: 2000 }

With this, you already have the tools to write simple queries and to add parameters to it.

Refer to Relationships and advanced filtering to learn how to add relationships and more advanced filters to this query.