Can I use variable for property name in driver cypher query?

For example:

def _find_by_node_property(tx, node_type, property_name, property_value):
        query = (
            "MATCH (p:Person) "
            "WHERE p.name = $person_name "
            "RETURN p.name AS name"
        )
        result = tx.run(query, person_name=property_name)
        return [record["name"] for record in result]

Here instead of using the explicit and 'person', 'p.name', can I use the variable 'node_type' & 'property_name' passed from the function argument? The intent is that I don't have to write a query function for each individual property. I general function with two arguments: property_name & property_value will be good enough.

One way I can think of would be to compose the query string first by concatenation:

query = (
            "MATCH (p:" + node_type + ") " + 
            "WHERE p." + property_name + "= " + $person_name " + 
            "RETURN p." + property_name + "AS name"
        )

This should work, but it seems awkward. Is there another way to do this?

1 Like

You can do this. To do it, we need to point out something simple and super-useful about Neo4j nodes.

A Neo4j node is a "Property Container" -- it can walk, talk, and act like a map.

So here's an example solution you could use with Neo4j browser to do this:

:param variablePropertyName => "name"

MATCH (p:Person)
WHERE p[$variablePropertyName] = "Bob"
RETURN p

Basically, the paramter doesn't get used in "dot syntax" (p.$somethingVariable) it gets used as a map index (p[$somethingVariable]) and then it works

Once you realize that nodes are actually maps of their properties, other interesting things become possible too, like keys(myNode) which gets the list of properties the node has.

1 Like

Be aware however that dynamic filtering in the WHERE clause like this will not be eligible for index lookups (for example if you had an index on :Person(name)). This is because the choice/evaluation of if an index can be used is decided by the planner, and the planner will not be considering parameter input, only the query.

2 Likes

great catch. Today I learned

So for efficiency consideration, dynamic filtering in the WHERE clause should not be used?

Not necessarily. There are many points in a query where we've already done an index lookup to find our starting nodes, and are now doing traversal and filtering, and in those cases a dynamic filtering with a WHERE clause would be just as effective as if it was hardcoded.

But when it comes to a node that you intend to be the starting point in the graph (the one that we need to lookup), it will have to use a label scan to find that node instead of a potential index seek.

So you've got to have an awareness of which nodes you intend to be starting places for lookup. In a query with several good potential starting nodes, you can probably afford to use a dynamic filtering on one of them and let the other(s) get looked up by index, but you should have a good understanding of your model and which starting nodes would be best accordingly.

Let's say I have a Product node type and it has 30 properties, i.e. price, color, etc. And any of the property could be used in the WHERE clause, because once it's published, it's unpredictable what properties users might use for restrictions.

Is this a typical use scenario? In such a case, how to avoid the potential problem above?

On the other hand, if I always avoid to use dynamic filtering due to its unpredictability, I would use this form only:

WHERE p.price = $price

In this case, in forming the query, I have to use a lots of 'if else' to make the property-name variable explicit:

if proper_name = 'price':
  WHERE = 'p.price = $property_value'
elif proper_name = 'color':
   WHERE = 'p.color = $property_value'
elif ...

For one node type, to do 30 if-else checking is not too bad. But given a few more node types, this quickly becomes unmanageable. The only way to solve this is to restrict users to filter on a small number of known properties?