Question about dynamic matching on relation and depth traversal (*x..y)

I’m using neo version 5.26.17

I’m trying to use dynamic matching because I want to create generic queries with parameterized relations. But I found this strange behavior, can somebody explain why it ignores the filter on relations if I set depth traversal ?

This is my model:

node(id:node_1) –hasChild→ node(id: node_2) –(rel:hasValueNode)→ node (id: value_node_1)

If I do this (with no set depth traversal), it only returns node_2, which seems correct

MATCH (n:DtEntity {id: 'node_2'})
OPTIONAL MATCH (n)-[r:$any(['hasChild'])]->(c)
return n, c, r

But if I do this, with ‘*’ on depth or ‘*1..2’ or any other depth traversal, it will return, node_2, value_node_1 and the hasValueNode-relation….(but I filter on hasChild relations)

MATCH (n:DtEntity {id: 'node_2'})
OPTIONAL MATCH (n)-[r:$any(['hasChild'])*]->(c)
return n, c, r

It will simply ignore the filter…?

You're seeing the correct behavior — Neo4j ignores your relationship type filter when you use variable-length patterns with parameters. This is because relationship types cannot be parameterized, especially when using *.

OPTIONAL MATCH (n)-[r:$any(['hasChild'])]->(c) -- This works

but when you add *

OPTIONAL MATCH (n)-[r:$any(['hasChild'])*]->(c)

Neo4j treats it as:

OPTIONAL MATCH (n)-[r*]->(c)

So it traverses all relationships, including hasValueNode

Official Neo4j Documentation (confirming this behavior)

Relationship types cannot be parameterized and link is -

I find that `OPTIONAL MATCH` sometimes makes things harder than it needs to be. You may just as well write:

match (n:DtEntity {id: 'node_2'})
return n,
collect {
    match path=(n)-[r:$any(['hasChild'])]->(c)
    return path
} as paths_to_children

Because your query says “return me the DtEntity regardless if it is connected to anything”.

Also worth checking newer variable length patterns syntax Variable-length patterns - Cypher Manual

Plus performance improvements introduced in 2025.08 MATCH - Cypher Manual

I want to create generic queries

Be careful with what you wish for :wink: . Generalisation does not always make your application better, faster, easier to maintain, …

I understand, thank you

Ah the newer syntax might be something I could work with, awesome! Thanks!

I’ll use apoc instead !

Want something generic, not familiar with cypher, over confidence in apoc. I have seen that in the past and it appears to be a road filled with surprises.

I wish we could get some time together to cover in more detail what you are looking to accomplish. Can you share some more context? You can also write me in private in a direct message if you want to discuss this further.

Sorry, I missed your response.

Thank you for helping me, I appreciate that!

We have one way relations, with a depth of max 10(-ish) in most cases.

The use case that i need solved in a good way:

  • From root-node with property id OR from node(s) with label
    • Look at param $relationTypes
      • Traverse down and get all nodes and relations (matching on relation-type)
        • Look at param labelFilter
          • get all nodes between root-node(s) and node(s) with labelFilter

I don’t know if it made sense, but here’s one query for example

$labelFilter here is with >{labelParam}:

MATCH(n: DtEntity:$any($modelIds))
                    CALL apoc.path.expandConfig(
                      n,{
                          relationshipFilter: apoc.text.join([relType IN $relationTypes | relType + '>'], '|'),
                          minLevel: 0,
                          maxLevel: 10,
                          bfs: true,
                          filterStartNode: false,
                          labelFilter: $labelFilter
                        }) yield path
 
                  WITH 
                         apoc.coll.toSet(
                            apoc.coll.flatten(
                                COLLECT(nodes(path)))) AS nodes,
                         apoc.coll.toSet(
                            apoc.coll.flatten(
                                COLLECT(relationships(path)))) AS relationships
                  RETURN nodes, relationships

So, given a node, you get a filtered sub graph around the node. Then what? I was expecting some more logic to be pushed down into the query so you get a result that is “not a graph” but rather “an answer to a question”.