Search code examples
pythonneo4jneomodel

Neomodel Most Efficient Way to Query Relationship Data


Suppose I have the following models from the neomodel documentation.

class FriendRel(StructuredRel):
    since = DateTimeProperty(
        default=lambda: datetime.now(pytz.utc)
    )
    met = StringProperty()

class Person(StructuredNode):
    name = StringProperty()
    friends = RelationshipTo('Person', 'FRIEND', model=FriendRel)

And I create the following data.

bob = Person(name='bob').save()
frank = Person(name='frank').save()
rel = bob.friends.connect(frank, {'since': dt.datetime.now(), 'met': 'Germany'})

Now my question is how I should go about retrieving both the friends of an object and the corresponding FriendshipRel objects between those relationships.

The Neomodel docs seem to say to do the following.

>>> bob = Person.nodes.get(name='bob')
>>> frank = bob.friends[0]  # get bob's friend frank using database query?
>>> rel = bob.friends.relationship(frank)  # query database again?
>>> rel.met
'Germany'

When doing this, it really feels like there would be a better way of retrieving relationship objects without another database query. I would expect these relationship objects to already be cached when you retrieve a node's friends?

So in a loop, would this be the best way to retrieve all of a Person's friends and the FriendshipRel objects for those friendships?

# source: https://stackoverflow.com/questions/67821341/retrieve-the-relationship-object-in-neomodel 
for friend in bob.friends:
    rel = bob.friends.relationship(friend)

This seems quite inefficient, as doesn't it require another database query for each relationship? Or am I not understanding correctly?

With cypher, I would just do the following:

MATCH(i:Person{name: 'bob'})-[j:FRIEND]->(k)  RETURN i,j,k

So my question: is there a way, using neomodel, to retrieve a node's relationships and the objects for those relationships both at the same time?


Solution

  • I've checked the neomodel source code and there doesn't seem to be a way to achieve what I want in a more efficient way than what I found in this stackoverflow answer.

    But I now know how to do this using cypher queries like so:

    from neomodel import db
    from models import Person, FriendRel
    
    bob = Person.nodes.get(name='bob')
    
    # Only one database query. Yay!
    results, cols = db.cypher_query(f"""MATCH (node)-[rel]-(neighbor)
                                        WHERE id(node)={john.id}
                                        RETURN node, rel, neighbor""")
    
    rels = {}  # friendships mapped to neighbor node ids
    neighbors = []
    
    for row in results:
        neighbor = Person.inflate(row[cols.index('neighbor')])
        neighbors.append(neighbor)
        rel = FriendRel.inflate(row[cols.index('rel')])
        rels[neighbor.id] = rel
    

    Then, now that you've stored all neighbors and the relationships between them, you can loop through them like so:

    for neighbor, rel in rels:
        print(f"bob has a friendship with {neighbor}.")
        print(f"They've been friends since {rel.since}")
    

    Or like so:

    for neighbor in neighbors:
        rel = rels[neighbor.id]
    

    Thanks to everyone's helpful advice!