cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.

How to "loop" query?

munshine
Node Clone

Hello 🙂

I am trying to achieve some sort of dynamic querying, but I'm a beginner, so maybe my request doesn't make sense or is not canonic.

So, I have a dictionnary of key/value pairs. This dictionnary lenght is not fixed. Sometimes if can be empty, sometimes it can have 2 rows, sometimes 10 rows.

I want to be able to forward this dictionnary to a Cypher query, and be able to "loop" through it, and only return item if all the MATCH have been successful.

Here is some (very) pseudo-code:

my_dictionnary = {
'file_type': 'scene',
'software': 'after-effect',
'category': 'character',
'task': 'color-grade',
}

for group_name, tag_name in my_dictionnary:
	MATCH (item:Item)-[:PARENT]->(group:Group {name: $group_name})-[:TAGGED]->(tag:Tag {name: $tag_name})

if all matches:
	RETURN item

Does that make sense, or am I going the wrong way? How could I achieve that?

(I never struggled that much to formulate a problem, that must be a bad sign)

Thanks a lot!

1 ACCEPTED SOLUTION

As Cypher is declarative rather than imperative, we don't have loops like this, but we can achieve something like this in other ways.

UNWIND, for example, will take each element of a list and turn it into a row. Since operations execute per row, it acts similar to a loop, though it isn't a looping structure, and it increases cardinality (since you still have one row per list element, or per the cartesian product of list elements with the rows they came from).

Check this article on performing match intersection with Cypher

It would also help to not pass a dictionary for this, but a list of pairs, that allows us to UNWIND the list to rows and work on them that way.

So for example, if your parameter was:

inputs: [{group:'file_type', tag:'scene'},
{group:'software', tag:'after-effect'},
{group:'category', tag:'character'},
{group:'task', tag:'color-grade'}]

Then we could work with it as a parameter via $inputs.

Assuming we have an index on either :Group(name) or :Tag(name), we could form a query like this:

UNWIND $inputs as input
MATCH (item:Item)-[:PARENT]->(group:Group {name: input.group})-[:TAGGED]->(:Tag {name: input.tag})
WITH item, count(group) as matches
WHERE size(matches) = size(input)
RETURN item

View solution in original post

2 REPLIES 2

As Cypher is declarative rather than imperative, we don't have loops like this, but we can achieve something like this in other ways.

UNWIND, for example, will take each element of a list and turn it into a row. Since operations execute per row, it acts similar to a loop, though it isn't a looping structure, and it increases cardinality (since you still have one row per list element, or per the cartesian product of list elements with the rows they came from).

Check this article on performing match intersection with Cypher

It would also help to not pass a dictionary for this, but a list of pairs, that allows us to UNWIND the list to rows and work on them that way.

So for example, if your parameter was:

inputs: [{group:'file_type', tag:'scene'},
{group:'software', tag:'after-effect'},
{group:'category', tag:'character'},
{group:'task', tag:'color-grade'}]

Then we could work with it as a parameter via $inputs.

Assuming we have an index on either :Group(name) or :Tag(name), we could form a query like this:

UNWIND $inputs as input
MATCH (item:Item)-[:PARENT]->(group:Group {name: input.group})-[:TAGGED]->(:Tag {name: input.tag})
WITH item, count(group) as matches
WHERE size(matches) = size(input)
RETURN item

Amazing!

I did not know about UNWIND, this is very powerful.

Thanks a lot