py2neo: Graph.find_one with multiple key/values
Asked Answered
T

3

5

I have some trouble with the py2neo find and find_one (http://py2neo.org/2.0/essentials.html)

What I want in Cypher is:

MATCH (p:Person) WHERE p.name='Alice' AND p.age=22 RETURN p

Say, where there are more than one key/value set (eg. if there are more than one 'Alice' in the graph).

My problem is that I don't know what to give graph.find_one, a working code is:

graph.find_one('Person', 'name', 'Alice')

What I would like is something like (This is not working!):

graph.find_one('Person', {'name': 'Alice', 'age': 22}) 

A possible (bad) solution would be to make a graph.find, and then loop through the results properties and look for the age, but I don't like that solution.

Bonus: Would it be possible with graph.find to do something like age > 25?


EDIT: New "solution"

find_person = "MATCH (p:Person) WHERE p.name = {N} AND p.age = {A} RETURN p"

>>> tx = graph.cypher.begin()
>>> tx.append(find_person, {'N': 'Alice', 'A': 22})
>>> res = tx.process()
>>> print(res[0][0][0])
(n423:Person {age:22,name:"Lisa"})

What I don't like about this is I miss the Note-object, (And I don't fully understand the RecordListList, and how to navigate it nicley)

Theroid answered 6/1, 2015 at 9:41 Comment(0)
T
2

Based on @elyase answer and the original py2neo.Graph.find, I've made this code. Please feel free to comment and improve.. :-)

def find_dict(graph, label, key_value=None, limit=None):
    """ Iterate through a set of labelled nodes, optionally filtering
    by property key/value dictionary
    """
    if not label:
        raise ValueError("Empty label")
    from py2neo.cypher.lang import cypher_escape
    if key_value is None:
        statement = "MATCH (n:%s) RETURN n,labels(n)" % cypher_escape(label)
    else:
        # quote string values
        d = {k: "'{}'".format(v) if isinstance(v, str) else v
             for k, v in key_value.items()}

        cond = ""
        for prop, value in d.items():
            if not isinstance(value, tuple):
                value = ('=', value)

            if cond == "":
                cond += "n.{prop}{value[0]}{value[1]}".format(
                    prop=prop,
                    value=value,
                )
            else:
                cond += " AND n.{prop}{value[0]}{value[1]}".format(
                    prop=prop,
                    value=value,
                )

        statement = "MATCH (n:%s ) WHERE %s RETURN n,labels(n)" % (
            cypher_escape(label), cond)
    if limit:
        statement += " LIMIT %s" % limit
    response = graph.cypher.post(statement)
    for record in response.content["data"]:
        dehydrated = record[0]
        dehydrated.setdefault("metadata", {})["labels"] = record[1]
        yield graph.hydrate(dehydrated)
    response.close()


def find_dict_one(graph, label, key_value=None):
    """ Find a single node by label and optional property. This method is
    intended to be used with a unique constraint and does not fail if more
    than one matching node is found.
    """
    for node in find_dict(graph, label, key_value, limit=1):
        return node

use of find_dict_one:

>>> a = find_dict_one(graph, 'Person', {'name': 'Lisa', 'age': 23})
>>>     print(a)
(n1:Person {age:23,name:"Lisa"})

Use of find_dict with tuple:

>>> a = find_dict(graph, 'Person', {'age': ('>', 21)}, 2)    >>> for i in a:
>>>     print(i)
(n2:Person {age:22,name:"Bart"})
(n1:Person {age:23,name:"Lisa"})

Use of find_dict without tuple:

>>> a = find_dict(graph, 'Person', {'age': 22}, 2)    >>> for i in a:
>>>     print(i)
(n2:Person {age:22,name:"Bart"})
Theroid answered 7/1, 2015 at 9:57 Comment(0)
P
5

If you look at the source code you will find that unfortunately find and find_one don't support that type of queries. You should directly use the Cypher interface:

d = {'name': 'Alice', 'age' : 22}

# quote string values
d = {k:"'{}'".format(v) if isinstance(v, basestring) else v 
                     for k,v in d.items()}

cond = ' AND '.join("p.{}={}".format(prop, value) for prop, value in d.items())

query = "MATCH (p:Person) {condition} RETURN p"
query = query.format(condition=cond)
# "MATCH (p:Person) p.age=22 AND p.name='Alice' RETURN p"
results = graph.cypher.execute(query)
Pyrexia answered 6/1, 2015 at 13:23 Comment(5)
Thanks for your answer.. :-) The problem with that solution is that it is really irradiating to past in parameters. I've made an edit to the post, with a bit better idea, but still not there yet.Theroid
@ThomasRepsdorph, I updated my answer to explain how to pass parameters, this is essentially what find should be doing, you might submit a pull request once you get it working.Pyrexia
@ThomasRepsdorph, YW, please don't forget to mark the answer as accepted if it solved your problem.Pyrexia
@Pyrexia is correct in pointing out that find and find_one do not support that type of query and that Cypher should be used in these cases instead. Methods such as find are really just sugar to make common use cases simpler and are not intended to be comprehensive replacements for Cypher.Babbage
Thanks for the answer. I had to add 'WHERE' in the template query to get it working. (i.e. query = "MATCH (p:Person) WHERE {condition} RETURN p" )Weaponeer
T
2

Based on @elyase answer and the original py2neo.Graph.find, I've made this code. Please feel free to comment and improve.. :-)

def find_dict(graph, label, key_value=None, limit=None):
    """ Iterate through a set of labelled nodes, optionally filtering
    by property key/value dictionary
    """
    if not label:
        raise ValueError("Empty label")
    from py2neo.cypher.lang import cypher_escape
    if key_value is None:
        statement = "MATCH (n:%s) RETURN n,labels(n)" % cypher_escape(label)
    else:
        # quote string values
        d = {k: "'{}'".format(v) if isinstance(v, str) else v
             for k, v in key_value.items()}

        cond = ""
        for prop, value in d.items():
            if not isinstance(value, tuple):
                value = ('=', value)

            if cond == "":
                cond += "n.{prop}{value[0]}{value[1]}".format(
                    prop=prop,
                    value=value,
                )
            else:
                cond += " AND n.{prop}{value[0]}{value[1]}".format(
                    prop=prop,
                    value=value,
                )

        statement = "MATCH (n:%s ) WHERE %s RETURN n,labels(n)" % (
            cypher_escape(label), cond)
    if limit:
        statement += " LIMIT %s" % limit
    response = graph.cypher.post(statement)
    for record in response.content["data"]:
        dehydrated = record[0]
        dehydrated.setdefault("metadata", {})["labels"] = record[1]
        yield graph.hydrate(dehydrated)
    response.close()


def find_dict_one(graph, label, key_value=None):
    """ Find a single node by label and optional property. This method is
    intended to be used with a unique constraint and does not fail if more
    than one matching node is found.
    """
    for node in find_dict(graph, label, key_value, limit=1):
        return node

use of find_dict_one:

>>> a = find_dict_one(graph, 'Person', {'name': 'Lisa', 'age': 23})
>>>     print(a)
(n1:Person {age:23,name:"Lisa"})

Use of find_dict with tuple:

>>> a = find_dict(graph, 'Person', {'age': ('>', 21)}, 2)    >>> for i in a:
>>>     print(i)
(n2:Person {age:22,name:"Bart"})
(n1:Person {age:23,name:"Lisa"})

Use of find_dict without tuple:

>>> a = find_dict(graph, 'Person', {'age': 22}, 2)    >>> for i in a:
>>>     print(i)
(n2:Person {age:22,name:"Bart"})
Theroid answered 7/1, 2015 at 9:57 Comment(0)
P
0

you can use NodeMatcher

person_node = NodeMatcher(gdb).match("Person").where(name=pname).first()
Pye answered 5/11, 2022 at 23:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.