How to transform an UNWIND query to FOREACH in Neo4J Cypher? - foreach

I have the following Neo4J Cypher query:
UNWIND $mentionsRelations as mention
MATCH (u:User{name:mention.from})
RETURN u.uid;
The params are:
{
"mentionsRelations": [
{
"from": "a",
"to": "b"
},
{
"from": "c",
"to": "d"
}
]
}
Is it possible to rewrite it to get the same results using the FOREACH query?
I know it doesn't accept the MATCH parameter, but I'm just curious if there's a workaround to get the same results?
Basically what I want is to reiterate through the mentionsRelations using FOREACH and to then output any matches in the database it uncovers.
Thanks!

Not currently possible, FOREACH is only for write operations and can't be used for just matching to nodes.
Is there some reason UNWIND won't work for you?
You can also do a match based upon list membership, which should work similarly, though you'd have to extract out the values to use for the property match:
WITH [mention in $mentionsRelationships | mention.from] as froms
MATCH (u:User)
WHERE u.name in froms
RETURN u.uid;

Related

Cypher: how do I check that at least one of the nodes in a set matches a given property?

I have a data model in neo4j where a Person node may be "merged" with another — not literally merged, just a relation in the form:
(a:Person)-[:MERGED]-(other:Person)
And, of course, b can be merged with someone else, in a potentially endless path.
I have a query to return a list of persons, with the 'merged' persons — that is, anyone in the :MERGED path — embedded as a property.
MATCH (a:Person)
CALL {
WITH a
MATCH path = (a)-[:MERGED*]-(other)
RETURN COLLECT(other{.label}) as b
}
RETURN a{.label, merged_items:b}
This returns, for example, something like:
{
"label": "John Smith",
"merged_items": [
{
"label": "Toby Jones"
},
{
"label": "Seamus McGibbon"
},
{
"label": "Aaron Drew"
}
]
}
for each of the Persons in this chain of merges (so actually the full result has four items, with each of the connected people being a — this is precisely what I want).
Now, I want to be able to filter the results by the Person.label, but any one of the Persons in the chain could match (either a OR any of the others).
Any idea how I might go about this?
I've tried a lot of different things (any(), for example) but can't get it to work.
The syntax for any() is WHERE any(e IN list WHERE predicate(e))
So in your case, this should work.
WITH COLLECT(other{.label}) as b
WHERE any(e IN b WHERE e.label = a.label)
RETURN b
You could in principle already apply it to the path before you collect. The tail(list) is so that it excludes a which would be the first node of the path.
MATCH ...
WHERE any(n in tail(nodes(path)) WHERE n.label = a.label)

How to write a boolean query in searchkick

I am trying to write a boolean query search, using searchkick, that returns all records where term A is mentioned along with term B or C,
A AND (B OR C)
After reading the docs it would seem that this is the format I need to match the above criteria,
conditions = { _and: [
{_or: [{title: "B"}, {title: 'C'}] }
]
}
Model.search "A", where: conditions
I would expect to see results returned for this query but no results are returned. I've double checked that there are documents that would meet the criteria. Any ideas on what needs to be tweaked?
A few references,
https://github.com/ankane/searchkick/issues/1284
https://github.com/ankane/searchkick/issues/1324

How to Create One to Many Relationships From JSON with Neo4j Cypher

I would like to create one to many relationships from JSON items in a file. Specifically, each JSON item contains an author and the id of books they have published. I have author nodes and book nodes that already exist in the database.
The data looks like:
{"id": "1", "name": "Dr. Suess", "books": [{"i": "100", "i": "101"}]}
{"id": "2", "name": "Shell Silverstein", "books": [{"i": "200", "i": "201"}]}
I am trying to import the nodes with the following code:
CALL apoc.load.json('file:/data.txt') YIELD value AS q
MATCH (a:Author {{id:q.id}})
UNWIND q.books as books
WITH a, books
MATCH (b:Books {{id:books.i}})
CREATE (a)-[:AUTHORED]->(b)
However, this is importing a fraction of the nodes I am expecting. Any suggestions on how to approach this problem would be greatly appreciated!
Well if you say that not all the authors and books are imported it means that the two MATCH statements don't find what they are looking for.
One possible scenario is that you have the IDs stored as an integer, but now you are trying to match them as a string. With the provided information, it is hard to assume anything else.
I would change the MATCH into MERGE statements to see if that is the problem.
CALL apoc.load.json('file:/data.txt') YIELD value AS q
MERGE (a:Author {{id:q.id}})
UNWIND q.books as books
WITH a, books
MERGE (b:Books {{id:books.i}})
CREATE (a)-[:AUTHORED]->(b)

Neo4j match Relationship parameters satisfying a certain schema

Using cypher, is there any way to match a path where the relationships satisfy a certain input schema generically?
I know I can do something like
parameters: {
"age": 20
}
MATCH (n)-[r:MY_RELATION]-() WHERE $age>18 AND $age<24 ...
when i want to match only relations satisfying the schema { "type": "integer", "minimum": 19, "maximum": 23 }.
But then i have to specify the min and max range within the relationship. What if I want to match strings against a schema, or even more complex types like address with subparameters etc.?
Is there a generic way to do it?
edit:
I'll try and state the question more clearly. What I want is a graph and a (parameterized) query to traverse that graph, such that:
i get all the nodes in the traversal
the relationships in the graph pose certain constraints on the query parameters (like minimum on an integer)
the traversal only follows edges where the constraint is met
i need to make constraints on integers, like min/max, but as well on strings, like pattern, etc.
edit 2:
What I want may not even be possible.
I want all of the information about the constraint to reside in the edge, including the parameter to test against. So I would want something along the lines of
parameters: { "age": 20, "location": "A" }
MATCH (n)-[r]-()
WHERE r.testtype='integer' AND getParameterByName(r.testparamname) < r.max
OR r.testtype='string' AND getParameterByName(r.testparamname)=r.allowedStringValue
Of course, as can be read in the neo4j documentation about parameter functionality it should not be possible to dynamically load the parameter via a name that resides in the DB.
There may yet be some workaround?
[UPDATED]
Original answer:
Your question is not stated very clearly, but I'll try to answer anyway.
I think something like this is what you want:
parameters: {
"minimum": 19,
"maximum": 23
}
MATCH (n)-[r:MY_RELATION]-() WHERE $maximum >= r.age >= $minimum
...
There is no need to specify a "type" parameter. Just make sure your parameter values are of the appropriate type.
New answer (based on updated question):
Suppose the parameters are specified this way (where test indicates the type of test):
parameters: {
"age": 20,
"test": "age_range"
}
Then you could do this (where r would contain the properties test, min, and max):
MATCH (n)-[r:MY_RELATION]-(m)
WHERE r.test = $test AND r.min <= $age <= r.max
RETURN n, r, m;
Or, if you do not need all the relationships to be of the same type, this should also work and may be easier to visualize (where r would be of, say, type "age_range", and contain the properties min and max):
MATCH (n)-[r]-(m)
WHERE TYPE(r) = $test AND r.min <= $age <= r.max
RETURN n, r, m;
To help you decide which approach to use, you should profile the two approaches with your code and some actual data to see which is faster for you.
Even Newer answer (based on edit 2 in question)
The following parameter and query should do what you want. Square brackets can be used to dyamically specify the name of a property.
parameters: {
"data": {
"age": 20,
"location": "A"
}
}
MATCH (n)-[r]-()
WHERE r.testtype='integer' AND $data[r.testparamname] < r.max
OR r.testtype='string' AND $data[r.testparamname]=r.allowedStringValue
...
Does this solution meet your requirements?
Considering the following small sample data set
MERGE (p1:Person {name: 'P 01'})
MERGE (p2:Person {name: 'P 02'})
MERGE (p3:Person {name: 'P 03'})
MERGE (p1)-[:MY_RELATION { minimum: 19, maximum: 23 }]->(p2)
MERGE (p2)-[:MY_RELATION { minimum: 19, maximum: 20 }]->(p3)
This query will only return the nodes and relationship where the supplied parameter fits the relationship constraints (e.g. $age = 21 should only return a single row). It is basically the inverse of #cybersam's proposal.
MATCH (s:Person)-[r:MY_RELATION]->(e:Person)
WHERE r.minimum <= $age <= r.maximum
RETURN *

Creating unique relationship property in a FOREACH in neo4j

I am trying to accomplish something quite simple in python but not so in Neo4j.
I'd appreciate any comment and suggestions to improve the procedure!
Within Python script, I am trying to create a relationship as well as its property for every pair of two nodes. From a data analysis (not a csv file), I ended up having a dataframe with three columns as following:
name1 name2 points
===========================
Jack Sara 0.3
Jack Sam 0.4
Jack Jill 0.2
Mike Jack 0.4
Mike Sara 0.5
...
From this point, I would like to create all nodes for the people: Jack, Sara, Sam, Mike, etc and as well as their relationship with a property name points.
First I tried to match all nodes and then use "FOREACH" to update the relationship property one at a time.
tx = graph.cypher.begin()
qs2 = "MATCH (p1:person {name:"Jack"}), (p2:person)
WHERE p2.name IN ["Sara","Jill","Mike"]
FOREACH (r IN range(10) |
CREATE (p1)-[:OWES TO {score:{score_list}[r]}]->(p2))"
Above statement does not return what I expected. Instead of matching one node to another, it calls all nodes in p2 and create the relationship between the paris, resulting multiple copies of the same information.
Is there a notation to indicate one node at a time? If you think there is a better approach than above, please do share with me. Thank you!
The easiest approach would be to export the data to be imported into csv file and use then the LOAD CSV command in cypher.
LOAD CSV WITH HEADERS FROM <url> AS csvLine
MATCH (p1:Person {name:csvLine.name1}), (p2:Person {name:csvLine.name2})
CREATE (p1)-[:OWES_TO {score:csvLine.points}]->(p2)
In case you cannot use that approach you can use a parameterized Cypher statement using the transactional http endpoint. The parameter is a single element map containing an array of your data structure. On http level the request body would look like:
{
"statements": [
{
"parameters": {
"data": [
{
"name1": "Jack", "name2": "Sara", "points": 0.3
},
{
"name1": "Jack", "name2": "Sam", "points": 0.4
},
{
"name1": "Jack", "name2": "Jill", "points": 0.2
} // ...
]
},
"statement": "UNWIND {data} AS row
MATCH (p1:Person {name:row.name1}), (p2:Person {name:row.name2})
CREATE (p1)-[:OWES_TO {row.points}]->(p2)"
}
]
}
update regarding comment below
Q: How can I create the parameters from pyhton?
A: use the python json module
import json
json.dumps({'data':[{'name1':'Jack', 'name2':'Sara', 'points':0.3},{'name1':'Jack', 'name2':'Sam', 'points':0.4}]})

Resources