Neo4j query concerning two elements - neo4j

I need help trying to do a query in Neo4j that I can't seem to figure out. The query is to return all cakes that contain both the ingredients: Milk and Cream.
Below is a snippet of a cake node and the ingredients (There are more ingredients and cakes but I didn't post them here as they are all formatted the same):
(brownies:Cake {name: "Brownies"}),
(brownies)-[:CONTAINS {quantity: 50, unit: "grams"}]->(white),
(brownies)-[:CONTAINS {quantity: 250, unit: "grams"}]->(selfraising),
(brownies)-[:CONTAINS {quantity: .5, unit: "grams"}]->(salt),
(brownies)-[:CONTAINS {quantity: 125, unit: "grams"}]->(sugar),
(brownies)-[:CONTAINS {quantity: 250, unit: "grams"}]->(cocoa),
(brownies)-[:CONTAINS {quantity: 125, unit: "grams"}]->(lemonade),
(brownies)-[:CONTAINS {quantity: 125, unit: "grams"}]->(cola),
(brownies)-[:GARNISHED_WITH {how: "chopped on top"}]->(cherry),
(brownies)-[:GARNISHED_WITH {how: "chopped on top"}]->(orange),
(limeJuice:Ingredient {name: "lime juice"}),
(cranberryJuice:Ingredient {name: "cranberry juice"}),
(lemonJuice:Ingredient {name: "lemon juice"}),
(orangeJuice:Ingredient {name: "orange juice"}),
(tomatoJuice:Ingredient {name: "tomato juice"}),
(lemonade:Ingredient {name: "lemonade"}),
(soda:Ingredient {name: "soda water"}),
(spice:Ingredient {name: "spice water"}),
(cola:Ingredient {name: "cola"}),
Neo4j seems to have trouble identifying ingredients but I'm not entirely sure that my query is formatted correctly regardless, here is what I have so far:
MATCH(x:Cake)-[:CONTAINS]-> (Ingredient: "milk" or "cream") Return x

Your Ingredient node check is problematic. Needs to be more like:
MATCH(x:Cake)-[:CONTAINS]-> (i:Ingredient)
WHERE i.name IN ['milk', 'cream']
Return x

Here is one way to get the cakes that contain ALL the ingredients from a list:
MATCH (cake:Cake)
WHERE ALL(x IN ['milk', 'cream'] WHERE (cake)-[:CONTAINS]->(:Ingredient{name: x}))
RETURN cake

Related

Neo4 cypher: Check if node with relationships to a list of node IDs exists

I have the following node structure: (:Patch)-[:INCLUDES]->(:Roster)-[:HAS]->(:PublicList)-[:INCLUDES]->(u:Unit)
Then I have an array of :Unit ids: [197, 196, 19, 20, 191, 171, 3, 174, 194, 185]
I would like to check whether a :PublicList that has the :INCLUDES relationship to all the :Unit ids in the list already exists.
I tried writing a COUNT and MATCH query like this, but this just seems like an error-prone long-winded approach:
MATCH (p:Patch)-[:INCLUDES]->(r:Roster)-[:HAS]-(d:PublicList)
WITH COLLECT(d) as drafts
UNWIND drafts as draft
WITH draft
UNWIND [197, 196, 19, 20, 191, 171, 3, 174, 194, 185] as unitID
MATCH (draft)-[:INCLUDES]->(u:Unit)
WHERE id(u) = unitID
WITH count(DISTINCT u) as draftUnits
WITH COLLECT(draftUnits) as matchCounts
RETURN matchCounts
Can someone help me write this so it returns a boolean if a :PublicList has a:INCLUDES relationship to all the IDs in the list?
I suggest to first match the units, put them into a collection and then use the ALL predicate to check that the PublicList has a connection to all units.
MATCH (n:Unit) WHERE id(n) IN [197, 196, 19, 20, 191, 171, 3, 174, 194, 185]
WITH collect(n) AS units
MATCH (p:Patch)-[:INCLUDES]->(r:Roster)-[:HAS]-(d:PublicList)
WHERE ALL(x IN units WHERE (d)-[:INCLUDES]->(x))
RETURN count(*) AS matchCount
If you want to return the PublicList along with a boolean value if it matches all of them, you can slightly adjust like this
MATCH (n:Unit) WHERE id(n) IN [197, 196, 19, 20, 191, 171, 3, 174, 194, 185]
WITH collect(n) AS units
MATCH (p:Patch)-[:INCLUDES]->(r:Roster)-[:HAS]-(d:PublicList)
RETURN d, ALL(x IN units WHERE (d)-[:INCLUDES]->(x)) as matchAll
Your query looks good but can be improved. Just to fix it, you need to use
u.id = unitID
instead of
WHERE id(u) = unitID
The latter is an internal id function which is uses a unique identification to all other nodes in the same database while the latter is a simple property named: id

Neo4j Cypher group by a column in a list of rows for aggregation

I have the following Neo4j Cypher query:
MATCH (v:Vacancy {deleted: false})-[vv:HAS_VOTE_ON]->(c:Criterion)<-[vp:HAS_VOTE_ON]-(p:Profile {id: 703, deleted: false})
WHERE vv.avgVotesWeight > 0 AND vv.avgVotesWeight <= vp.avgVotesWeight
WITH v, p
MATCH (v)-[vv1:HAS_VOTE_ON]->(cv:Criterion)
OPTIONAL MATCH (p)-[vp1:HAS_VOTE_ON]->(cv)
WITH v.id as vacancyId, cv.id as criterionId, coalesce(vv1.`properties.skillCoefficient`, 1.0) as vacancyCriterionCoefficient, coalesce(vp1.avgVotesWeight, 0) as profileCriterionVoteWeight, coalesce(vp1.totalVotes, 0) as profileCriterionTotalVotes
RETURN vacancyId, criterionId, vacancyCriterionCoefficient, profileCriterionVoteWeight, profileCriterionTotalVotes
which returns the following values:
Now, for each Vacancy (with the same vacancyId) I need to calculate totalProfileCriterionVoteWeight (SUM) for all criteria by the folowing formula:
vacancyCriterionCoefficient * profileCriterionVoteWeight
For this purpose, I need to group somehow the rows by vacancyId.
Could you please show how it is possible with a Cypher here?
You can replace your last line with:
WITH distinct(vacancyId) as vacancyId, sum(vacancyCriterionCoefficient * profileCriterionVoteWeight) as totalProfileCriterionVoteWeight
RETURN vacancyId, totalProfileCriterionVoteWeight
Which For the data shown in the picture will return:
╒═══════════╤═════════════════════════════════╕
│"vacancyId"│"totalProfileCriterionVoteWeight"│
╞═══════════╪═════════════════════════════════╡
│704 │22 │
├───────────┼─────────────────────────────────┤
│706 │16 │
└───────────┴─────────────────────────────────┘
Explanation: distinct allows to "group" the rows, only with an "accumulator" to other fields. Here we just needed to use SUM as an accumulator.
In order to test it, I used sample data:
MERGE (a:Node{vacancyId:704, criterionId: 6907, vacancyCriterionCoefficient: 1, profileCriterionVoteWeight: 1, profileCriterionTotalVotes: 1})
MERGE (b:Node{vacancyId:704, criterionId: 6909, vacancyCriterionCoefficient: 3, profileCriterionVoteWeight: 5, profileCriterionTotalVotes: 1})
MERGE (c:Node{vacancyId:704, criterionId: 6908, vacancyCriterionCoefficient: 2, profileCriterionVoteWeight: 3, profileCriterionTotalVotes: 1})
MERGE (d:Node{vacancyId:706, criterionId: 6909, vacancyCriterionCoefficient: 1, profileCriterionVoteWeight: 5, profileCriterionTotalVotes: 1})
MERGE (e:Node{vacancyId:706, criterionId: 6908, vacancyCriterionCoefficient: 3, profileCriterionVoteWeight: 3, profileCriterionTotalVotes: 1})
MERGE (f:Node{vacancyId:706, criterionId: 6907, vacancyCriterionCoefficient: 2, profileCriterionVoteWeight: 1, profileCriterionTotalVotes: 1})
And query:
MATCH (n)
WITH n.vacancyId as vacancyId, n.criterionId as criterionId, n.vacancyCriterionCoefficient as vacancyCriterionCoefficient, n.profileCriterionVoteWeight as profileCriterionVoteWeight, n.profileCriterionTotalVotes as profileCriterionTotalVotes
WITH distinct(vacancyId) as vacancyId, sum(vacancyCriterionCoefficient * profileCriterionVoteWeight) as totalProfileCriterionVoteWeight
//return vacancyId, criterionId, vacancyCriterionCoefficient, profileCriterionVoteWeight, profileCriterionTotalVotes
RETURN vacancyId, totalProfileCriterionVoteWeight
Which provide the results above

Neo4j: Match Merge throwing Neo.ClientError.Statement.SyntaxError

I was trying to run a query in Neo4j to make a relationship between a recipe and ingredients:
MATCH (spongeCake:Cake {name: "Sponge Cake"}),
(white:Flour {name: "white"}),
(egg:Ingredient {name: "egg"}),
(butter:Ingredient {name: "butter"}),
(milk:Ingredient {name: "milk"}),
(sugar:Ingredient {name: "sugar"}),
(brown:Flour {name: "brown"}),
MERGE (spongeCake)-[r:CONTAINS {quantity: 4, unit: "medium"}]->(egg),
(spongeCake)-[r:CONTAINS {quantity: 50, unit: "grams"}]->(brown),
(spongeCake)-[r:CONTAINS {quantity: 255, unit: "grams"}]->(sugar),
(spongeCake)-[r:CONTAINS {quantity: 25, unit: "grams"}]->(milk),
(spongeCake)-[r:CONTAINS {quantity: 300, unit: "grams"}]->(white),
(spongeCake)-[r:CONTAINS {quantity: 45, unit: "grams"}]->(butter);
For some reason MERGE is giving me a lot of trouble and I am getting the following error:
Invalid input 'MERGE': expected "(", "allShortestPaths" or "shortestPath" (line 9, column 1 (offset: 250))
"MERGE (spongeCake)-[r:CONTAINS {quantity: 4, unit: "medium"}]->(egg)"
^
How can I do this correctly?
This is the syntax of what you want to achieve.
There is a comma before Merge so it will not work.
Too many commas in match so it will create cartesian products
Many commas in MERGE and it will not work, so I removed it.
Learn the syntax well. Goodluck!
..
MATCH (spongeCake:Cake {name: "Sponge Cake"})
MATCH (white:Flour {name: "white"})
MATCH (egg:Ingredient {name: "egg"}),
MATCH (butter:Ingredient {name: "butter"})
MATCH (milk:Ingredient {name: "milk"})
MATCH (sugar:Ingredient {name: "sugar"})
MATCH (brown:Flour {name: "brown"})
MERGE (spongeCake)-[:CONTAINS {quantity: 4, unit: "medium"}]->(egg)
MERGE (spongeCake)-[:CONTAINS {quantity: 50, unit: "grams"}]->(brown)
MERGE (spongeCake)-[:CONTAINS {quantity: 255, unit: "grams"}]->(sugar)
MERGE (spongeCake)-[:CONTAINS {quantity: 25, unit: "grams"}]->(milk)
MERGE (spongeCake)-[:CONTAINS {quantity: 300, unit: "grams"}]->(white)
MERGE (spongeCake)-[:CONTAINS {quantity: 45, unit: "grams"}]->(butter)
RETURN spongeCake

Elixir Accumulator List of Maps

Can you help me to implement one Accumulator from List of maps?.
[
%{
score: 1,
name: "Javascript",
},
%{
score: 2,
name: "Elixir",
},
%{
score: 10,
name: "Elixir",
}
]
The result should be:
[
%{
score: 12,
name: "Elixir",
},
%{
score: 1,
name: "Javascript",
}
]
I will appreciate your suggestion.
Regards
Assuming your original list is stored in input local variable, one might start with Enum.reduce/3 using Map.update/4 as a reducer.
Enum.reduce(input, %{}, fn %{score: score, name: name}, acc ->
Map.update(acc, name, score, & &1 + score)
end)
#⇒ %{"Elixir" => 12, "Javascript" => 1}
Whether you insist on having a list of maps as a result (which is way less readable IMSO,) go further and Enum.map/2 the result:
Enum.map(%{"Elixir" => 12, "Javascript" => 1}, fn {name, score} ->
%{name: name, score: score}
end)
#⇒ [%{name: "Elixir", score: 12},
# %{name: "Javascript", score: 1}]
To sum it up:
input
|> Enum.reduce(%{}, fn %{score: score, name: name}, acc ->
Map.update(acc, name, score, & &1 + score)
end)
|> Enum.map(& %{name: elem(&1, 0), score: elem(&1, 1)})
#⇒ [%{name: "Elixir", score: 12},
# %{name: "Javascript", score: 1}]
Sidenote: maps in erlang (and, hence, in elixir) are not ordered. That means, if you want the resulting list to be sorted by name, or by score, you should explicitly Enum.sort/2 it:
Enum.sort(..., & &1.score > &2.score)
#⇒ [%{name: "Elixir", score: 12},
# %{name: "Javascript", score: 1}]
A simple way could be to use Enum.group_by/3 to group the items by name, then Enum.sum/1 to sum the scores:
list
|> Enum.group_by(& &1.name, & &1.score)
|> Enum.map(fn {name, score} -> %{name: name, score: Enum.sum(score)} end)
Output:
[%{name: "Elixir", score: 12}, %{name: "Javascript", score: 1}]
If you were looking to create & use a more generalized solution, you could create your own Merger module.
defmodule Merger do
def merge_by(enumerable, name_fun, merge_fun) do
enumerable
|> Enum.group_by(name_fun)
|> Enum.map(fn {_name, items} -> Enum.reduce(items, merge_fun) end)
end
end
list = [
%{score: 1, name: "Javascript"},
%{score: 2, name: "Elixir"},
%{score: 10, name: "Elixir"}
]
Merger.merge_by(list, & &1.name, &%{&1 | score: &1.score + &2.score})
# => [%{name: "Elixir", score: 12}, %{name: "Javascript", score: 1}]

py2neo unique nodes with unique relations given timestamp

I am trying to create a graph that stores time based iterations between nodes. I would like the nodes to be unique and relationships between nodes to be unique given the timestamp property.
My first attempt creates 2 nodes and 1 relationship which is not what I want.
from py2neo import neo4j, node, rel
graph_db = neo4j.GraphDatabaseService()
graph_db.get_or_create_index(neo4j.Node, "node_index")
batch = neo4j.WriteBatch(graph_db)
# a TALKED_TO b at timestamp 0
batch.get_or_create_indexed_node('node_index', 'name', 'a', {'name': 'a'})
batch.get_or_create_indexed_node('node_index', 'name', 'b', {'name': 'b'})
batch.get_or_create_indexed_relationship('rel_index', 'type', 'TALKED_TO', 0, 'TALKED_TO', 1, {"timestamp": 0})
# a TALKED_TO b at timestamp 1
batch.get_or_create_indexed_node('node_index', 'name', 'a', {'name': 'a'})
batch.get_or_create_indexed_node('node_index', 'name', 'b', {'name': 'b'})
batch.get_or_create_indexed_relationship('rel_index', 'type', 'TALKED_TO', 3, 'TALKED_TO', 4, {"timestamp": 1})
# a TALKED_TO b at timestamp 2
batch.get_or_create_indexed_node('node_index', 'name', 'a', {'name': 'a'})
batch.get_or_create_indexed_node('node_index', 'name', 'b', {'name': 'b'})
batch.get_or_create_indexed_relationship('rel_index', 'type', 'TALKED_TO', 6, 'TALKED_TO', 7, {"timestamp": 0})
results = batch.submit()
print results
#[Node('http://localhost:7474/db/data/node/2'),
#Node('http://localhost:7474/db/data/node/3'),
#Relationship('http://localhost:7474/db/data/relationship/0'),
#Node('http://localhost:7474/db/data/node/2'),
#Node('http://localhost:7474/db/data/node/3'),
#Relationship('http://localhost:7474/db/data/relationship/0'),
#Node('http://localhost:7474/db/data/node/2'),
#Node('http://localhost:7474/db/data/node/3'),
#Relationship('http://localhost:7474/db/data/relationship/0')]
My second attempt creates 2 nodes and 0 relations, not sure why it fails to create any relationships.
from py2neo import neo4j, node, rel
graph_db = neo4j.GraphDatabaseService()
graph_db.get_or_create_index(neo4j.Node, "node_index")
batch = neo4j.WriteBatch(graph_db)
# a TALKED_TO b at timestamp 0
batch.get_or_create_indexed_node('node_index', 'name', 'a', {'name': 'a'})
batch.get_or_create_indexed_node('node_index', 'name', 'b', {'name': 'b'})
batch.create(rel(0, 'TALKED_TO', 1, {"timestamp": 0}))
# a TALKED_TO b at timestamp 1
batch.get_or_create_indexed_node('node_index', 'name', 'a', {'name': 'a'})
batch.get_or_create_indexed_node('node_index', 'name', 'b', {'name': 'b'})
batch.create(rel(3, 'TALKED_TO', 4, {"timestamp": 1}))
# a TALKED_TO b at timestamp 2
batch.get_or_create_indexed_node('node_index', 'name', 'a', {'name': 'a'})
batch.get_or_create_indexed_node('node_index', 'name', 'b', {'name': 'b'})
batch.create(rel(6, 'TALKED_TO', 7, {"timestamp": 0}))
results = batch.submit()
print results
#[Node('http://localhost:7474/db/data/node/2'),
#Node('http://localhost:7474/db/data/node/3'),
#None]
So how do I achieve what is depicted in the image below?
Okay so I think I figured it out but I'm not sure if its efficient. Does anyone know a better way than the following?
# Create nodes a and b if they do not exist.
query = """MERGE (p:Person { name: {name} }) RETURN p"""
cypher_query = neo4j.CypherQuery(neo4j_graph, query )
result = cypher_query .execute(name='a')
result = cypher_query .execute(name='b')
# Create a relationship between a and b if it does not exist with the given timestamp value.
query = """
MATCH (a:Person {name: {a}}), (b:Person {name: {b}})
MERGE (a)-[r:TALKED_TO {timestamp: {timestamp}}]->(b)
RETURN r
"""
cypher_query = neo4j.CypherQuery(neo4j_graph, query)
result = cypher_query.execute(a='a', b='b', timestamp=0)
result = cypher_query.execute(a='a', b='b', timestamp=1)

Resources