1
votes

I am trying to do a basic retrieval of a node from my Neo4j database using py2neo and a cypher call, but I can't find any examples of working code for py2neo v4.1 and the documentation has no real descriptions, examples, helpful information, or links to tutorials. Similar questions on SE utilize code that no longer works.

I have the following cypher query:

getCityNodeQuery= '''MATCH (State) WHERE n.state_name=$sttnm
OPTIONAL MATCH (n:State)<-[:PARTOF]-(county:County) WHERE county.county_name CONTAINS $ctynm
OPTIONAL MATCH (n:State)<-[:PARTOF]-(city:City) WHERE city.city_name CONTAINS $ctynm
OPTIONAL MATCH (n:State)<-[:PARTOF]-(county:County)<-[:PARTOF]-(citycounty:Ward) WHERE citycounty.city_name CONTAINS $ctynm
RETURN county, city, citycounty'''

When I run this in the Neo4j desktop browser with values put in for the sttnm and ctynm fields I always get exactly what I'm looking for: a single node representing that city (for example inputting "Florida" and "Gainesville" brings up the node for the city of Gainesville).

So the Cypher part itself seems correct, and the problem is therefore probably with how I'm calling it from py2neo:

def getCityWardNode(prefecture_name, city_name):
    thisCityNode = graph.evaluate(getCityNodeQuery, parameters = {'sttnm':state_name, 'ctynm':city_name})
    print(thisCityNode)

which returns None.

So one thought is that I'm not calling the query correctly, and so it's not returning the node that it does when called from Neo4j browser. However, when I run just the first line and RETURN n I do get the correct County node, so my use of graph.evaluate() and my way of passing parameters both seem correct.

I can also rearrange the query to make the County a condition on matching the City and THAT works and avoids the optional match. I reformulated the query a few ways and in one way I got the Counties, but not Cities within Counties. Unfortunately I actually have three different conditions that I want to match optionally. So the most immediate question is:

Does py2neo not support OPTIONAL MATCH within Cypher queries?

So the more general question is:

How do I return the desired node from my Neo4j database using cypher within py2neo?

Or

What are the differences between using Cypher queries in Neo4j Browser vs py2neo?

1
did you read this? The example is on the first line: py2neo.org/v4/database.html#the-graphHimanshu Jain
I updated my question based on new attempts and clarified the problem. The example I think you are pointing to in the documentation using graph.run() doesn't include parameters and hence doesn't explain how to properly include them. It also doesn't save the result to a variable. Also, I don't have a problem getting some nodes from a Cypher query in py2neo, but this py2neo cypher query doesn't return the same thing that Neo4j does, and I don't know why or how to fix it.Aaron Bramson
just wondering if the newline characters in the query string are creating a problem. Can you try a single line query? Although the same query is working for me, so I am not sure what is different. If that doesn't work, please share entire code along with sample queries for creating nodes. I was unable to reproduce the issue.Himanshu Jain
I tried a single line query, but I get the same result: returns None. I've edited my question again with more info based on what I've tried so far.Aaron Bramson
can you share queries to create sample data too. I checked the queries you mentioned before your recent edit. I see previously you were using "County_name" and now you use "county_name". There might be other things as well. I want to make sure what you have on nodes is what you are using in your query in py2neo. When I create my own sample data, I am unable to reproduce it. It will be better if you share your sample data.Himanshu Jain

1 Answers

3
votes

The solution to this problem has to do with the objects returned by the various commands in py2neo v 4.1.0. I've tried to generalize the problem/solution to be helpful beyond my particular use-case.

Running the Cypher command in neo4j browser returns a RECORD object and all the nodes and edges (and in the Browser it seems all edges among all the nodes found even if you didn't ask for those edges). The browser will show you all the items in that record without you having to do anything special (though you can limit what the browser returns using LIMIT for the number and WHERE for filtering labels and properties).

Py2neo has a variety of options for returning objects from Cypher queries, none of which are well documented and none of which have useful examples or sufficient explanations of differences. But after lots of trying and failing I managed to figure out a few things and get it to work. I'm going to share what I learned so that hopefully somebody else trying to use this package doesn't lose hours due to poor documentation.

Consider the following method you might use to retrieve a node from your database.

import py2neo as pn
graph = pn.Graph("bolt://localhost:####/", user="neo4j", password="pwd")

theCypherQuery= '''MATCH (n:Label1) WHERE n.label1_name=$para1 
OPTIONAL MATCH (n:Label1)<-[:REL1]-(n2:Label2) WHERE n2.label2_name = $para2
OPTIONAL MATCH (n:Label1)<-[:REL1]-(n3:Label3) WHERE n3.label3_name = $para2
RETURN n2, n3'''

def getNode(thisCypherQuery, parameter1, parameter2):
    cypherResult = graph.evaluate(thisCypherQuery, parameters = {'para1':parameter1, 'para2':parameter2})
    return cypherResult

someNode = getNode(theCypherQuery,firstParameter,secondParameter)

If theCypherQuery always returns exactly one node, then graph.evaluate will work because it actually returns the first object in the record generated by the query.

However, if you have a more complicated query and/or database that potentially returns multiple items (even if all but one item is None) then you need to use graph.run instead of graph.evaluate. But graph.run returns a record object that is not something you can deal with in Python easily, so there are options:

  1. graph.run(theCypherQuery).data() returns the result as a list of one dictionary reporting all the nodes returned in the record.

  2. graph.run(theCypherQuery).table() returns that dictionary as a table of keys and values, which seems mostly useful for printing to the console.

  3. graph.run(theCypherQuery).evaluate() is equivalent to graph.evaluate(theCypherQuery) above and returns the first object.

In my real case I wanted to match a name across nodes with different kinds of labels that are children of another node with a particular label. My Cypher query always returned the correct node, but there were five None objects returned as well (for the other node labels) that were simply ignored in the Browser but were breaking my Python code. Maybe I could change my Cypher query to only return that one node regardless of its label type, but regardless I figured it's a good idea to learn how to deal with these record objects.

Here is an example of manipulating the returned record object in Python to extract nodes and eliminate the None responses.

def getNode(thisCypherQuery, parameter1, parameter2):
## The returned node is None by default
thisNode = None  
## Retrieve the record object from the query, substituting in the parameter values.
## The .data() part returns a list containing a single dictionary.
## So I extract the dictionary by simply pulling item [0].
thisRecord = graph.run(theCypherQuery, parameters = {'para1':parameter1, 'para2':parameter2}).data()[0]
## Now I create a list of non-None values from the dictionary using "list comprehension".
theseNodes = [val for key,val in thisRecord .items() if val != None]
## Perhaps nothing was found, but if at least one item was found...
if len(theseNodes) > 0:  
    ## Then return the first found object (which in my case is the unique matching node)
    ## Note that this is also necessary to convert the list into a node object.
    thisNode = theseNodes[0]
return thisNode 

Nothing here is especially strange of difficult Python if you are already proficient, but without proper documentation it may be difficult to understand the data structures and what is necessary to retrieve and manipulate them. In this case, the retrieved node objects are compatible with py2neo commands such as the following to create a link between two found nodes based on their names (from another data source).

firstNode = getNode(theCypherQuery,'UnitedStates','Georgia')
secondNode = getNode(theCypherQuery,'UnitedStates','Jacksonville')
graph.merge(pn.Relationship(firstNode,'BORDERING',secondNode))

Note that I haven't tried to return and manipulate a relationship object yet, but hopefully it won't be too different from getting and using the node objects. And hopefully you can modify this code to retrieve nodes and relationships to fit your needs.