Cypher merge nodes with same property and collected the other property - neo4j

I have nodes with this structure
(g:Giocatore { nome, match, nazionale})
(nome:'Del Piero', match:'45343', nazionale:'ITA')
(nome:'Messi', match:'65324', nazionale:'ARG')
(nome:'Del Piero', match:'18235', nazionale:'ITA')
The property 'match' is unique (ID's of match) while there are several 'nome' with the same name.
I want to merge all the nodes with the same 'nome' and create a collection of different 'match' like this
(nome:'Del Piero', match:[45343,18235], nazionale:'ITA')
(nome:'Messi', match:'65324', nazionale:'ARG')
I tried with apoc library too but nothing works.
Any idea?

Can you try this query :
MATCH (n:Giocatore)
WITH n.nome AS nome, collect(n) AS node2Merge
WITH node2Merge, extract(x IN node2Merge | x.match) AS matches
CALL apoc.refactor.mergeNodes(node2Merge) YIELD node
SET node.match = matches
Here I'm using APOC to merge the nodes, but then I do a map transformation on the node list to have an array of match, and I set it on the merged node.
I don't know if you have a lot of Giocatore nodes, so perhaps this query will do an OutOfMemory exception, so you will have to batch your query. You can for example replace the first line by MATCH (n:Giocatore) WHERE n.nome STARTS WITH 'A' and repeat it for each letter or you can also use the apoc.periodic.iterate procedure :
CALL apoc.periodic.iterate(
'MATCH (n:Giocatore) WITH n.nome AS nome, collect(n) AS node2Merge RETURN node2Merge, extract(x IN node2Merge | x.match) AS matches',
'CALL apoc.refactor.mergeNodes(node2Merge) YIELD node
SET node.match = matches',
{batchSize:1000,parallel:true,retries:3,iterateList:true}
) YIELD batches, total

Related

Return non-matching nodes while creating relationships Neo4j Cypher

I wrote a script to to batch create a bunch of relationship in neo4j. Here is the cypher:
:param batch => [{startId: 'abc123', endId: 'abc321'}, {startId: 'abc456', endId: 'abc654']
UNWIND $batch as row
MATCH (from {id: row.startId}
MATCH (to {id: row.endId}
CREATE (from)-[rel:HAS]->(to)
RETURN rel
The problem that there might be some startId/endId entries that don't match any nodes and are silently ignore. Is there a way to return the list of rows that don't match any nodes and create the relationship for the nodes that do match?
I tried OPTIONAL MATCH to fail-fast as soon an id doesn't find a startId/endId however, the query execution was really slow.
First of all, you should always try to specify a label for the node that is used to kick off a MATCH (unless the MATCH pattern uses any already-bound nodes). Otherwise, every single node in the DB must be scanned. In addition, you should consider using indexes to speed up your MATCHs (but, again, you'd need to specify the labels).
Here is a query that uses the APOC procedure apoc.do.when to create a new relationship when appropriate. It returns each row and the corresponding new relationship (or NULL if either node is not found):
UNWIND $batch as row
OPTIONAL MATCH (from:Foo {id: row.startId})
OPTIONAL MATCH (to:Foo {id: row.endId})
CALL apoc.do.when(
from IS NOT NULL AND to IS NOT NULL,
'CREATE (from)-[rel:HAS]->(to) RETURN rel',
'RETURN NULL AS rel',
{from: from, to: to}) YIELD value
RETURN row, value.rel AS rel

How Many Nodes Are Involved in a Match

How can I know how many nodes and edges are involved in a MATCH? Is there another way besides Explain / Profile Match?
If you mean how many nodes are matched in a path, such as a variable-length path, then you can assign a path variable for this:
MATCH p = (k:Person {name:'Keanu Reeves'})-[*..8]-(t:Person {name:'Tom Hanks'})
WITH p LIMIT 1
RETURN p, length(p) as pathLength, length(p) + 1 as numberOfNodesInPath
You can also use nodes(p) and relationships(p) to get the collection of nodes and relationships that make up the path, and you can use size() on those collections to get their size.
There exists the COUNT() function of Cypher that allows you to count the number of elements. As for example in this query:
MATCH (n)
RETURN COUNT(n);
This query will count all nodes in your database.
You can find more information in the cypher manual, under the aggregating functions. Check it out.
The following Cypher snippet should return the number of distinct nodes and relationships found by any given MATCH clause. Just replace <your code here> with your MATCH pattern.
MATCH <your code here>
WITH COLLECT(NODES(p)) AS ns, SUM(SIZE(RELATIONSHIPS(p))) AS relCount
UNWIND ns AS nodeList
UNWIND nodeList AS node
RETURN COUNT(DISTINCT node) AS nodeCount, relCount;

cypher NOT IN query with Optional Match

NOT RELEVANT - SKIP TO Important Edit.
I have the following query:
MATCH (n)
WHERE (n:person) AND n.id in ['af97ab48544b'] // id is our system identifier
OPTIONAL MATCH (n)-[r:friend|connected|owner]-(m)
WHERE (m:person OR m:dog OR m:cat)
RETURN n,r,m
This query returns all the persons, dogs and cats that have a relationship with a specific person. I would like to turn it over to receive all the nodes & relationships that NOT includes in this query results.
If it was SQL it would be
select * from graph where id NOT IN (my_query)
I think that the OPTIONAL MATCH is the problematic part. I How can I do it?
Any advice?
Thanks.
-- Important Edit --
Hey guys, sorry for changing my question but my requirements has been changed. I need to get the entire graph (all nodes and relationships) connected and disconnected except specific nodes by ids. The following query is working but only for single id, in case of more ids it isn't working.
MATCH (n) WHERE (n:person)
OPTIONAL MATCH (n)-[r:friend|connected|owner]-(m) WHERE (m:person OR m:dog OR m:cat)
WITH n,r,m
MATCH (excludeNode) WHERE excludeNode.id IN ['af97ab48544b']
WITH n,r,m,excludeNode WHERE NOT n.id = excludeNode.id AND (NOT m.id = excludeNode.id OR m is null)
RETURN n,m,r
Alternatively I tried simpler query:
MATCH (n) WHERE (n:person) AND NOT n.id IN ['af97ab48544b'] return n
But this one does not returns the relationships (remember I need disconnected nodes also).
How can I get the entire graph exclude specific nodes? That includes nodes and relationships, connected nodes and disconnected as well.
try this:
match (n) where not n.id = 'id to remove' optional match (n)-[r]-(m)
where not n.id in ['id to remove'] and not m.id in ['id to remove']
return n,r,m
You've gotta switch the 'perspective' of your query... start by looping over every node, then prune the ones that connect to your person.
MATCH (bad:person) WHERE bad.id IN ['af97ab48544b']
WITH COLLECT(bad) AS bads
MATCH path = (n:person) - [r:friend|:connected|:owner] -> (m)
WHERE n._id = '' AND (m:person OR m:cat OR m:dog) AND NOT ANY(bad IN bads WHERE bad IN NODES(path))
RETURN path
That said, this is a problem much more suited to SQL than to a graph. Any time you have to loop over every node with a label, you're in relational territory, the graph will be less efficient.

Including vars in Neo4j WITH statement changes query output

I'm trying to find the number of nodes of a certain kind in my database that are connected to more than one other node of another kind. In my case, it's place nodes connected to several name nodes. I have a query that works:
MATCH rels=(p:Place)-[c:Called]->(n:Name)
WITH p,count(n) as counts
WHERE counts > 1
RETURN p;`
However, that only returns the place nodes, and ideally I'd like it to return all the nodes and edges involved. I've found a question on returning variables from before the WITH, but if I include any of the other variables I've defined, the query returns no responses, i.e. this query returns nothing:
MATCH rels=(p:Place)-[c:Called]->(n:Name)
WITH p, count(n) as counts, rels
WHERE counts > 1
RETURN p;
I don't know how to return the information that I want without changing the results of the query. Any help would be much appreciated
The reason your second query returns nothing is because its WITH clause specifies as aggregation "grouping keys" both p and rels. Since each rels path has only a single n value, counts would always be 1.
Something like this might work for you:
MATCH path=(p:Place)-[:Called]->(:Name)
WITH p, COLLECT(path) as paths
WHERE SIZE(paths) > 1
RETURN p, paths;
This returns each matching Place node and all its paths.
Try this:
MATCH (p:Place)-[c:Called]->(n:Name)
WHERE size((p)-[:Called]->(:Name)) > 1
WITH p,count(n) as counts, collect(n) AS names, collect(c) AS calls
RETURN p, names, calls, counts ORDER BY counts DESC;
This query makes use of Cypher's collect() function to create lists of the names and called relationships for each place that has more than Called relationship with a Name node.

neo4j collecting nodes and relations type b-->a<--c,a<--d

I am extending maxdemarzi's excellent graph visualisation example (http://maxdemarzi.com/2013/07/03/the-last-mile/) using VivaGraph backed by neo4j.
I want to display relationships of the type
a-->b<--c,b<--d
I tried the query
MATCH p = (a)--(b:X)--(c),(b:X)--(d)
RETURN EXTRACT(n in nodes(p) | {id:ID(n), name:COALESCE(n.name, n.title, ID(n)), type:LABELS(n)}) AS nodes,
EXTRACT(r in relationships(p)| {source:ID(startNode(r)) , target:ID(endNode(r))}) AS rels
It looks like the named query picks up only a-->b<--c pattern and omits the b<--d patterns.
Am i missing something... can i not add multiple patterns in a named query?
The most immediate problem is that the comma in the MATCH clause separates the first pattern from the second. The variable 'p' only stores the first pattern. This is why you aren't getting the results you desire. Independent of that, you are at risk of having a 'loose binding' by putting a label on both of your nodes named 'b' in the two patterns. The second 'b' node should not have a label.
So here is a version of your query that should work.
MATCH p1=(a)-->(b:X)<--(c), p2=(b)<--(d)
WITH nodes(p1) + d AS ns, relationships(p1) + relationships(p2) AS rs
RETURN EXTRACT(n IN ns | {id:ID(n), name:COALESCE(n.name, n.title, ID(n)), type:LABELS(n)}) AS nodes,
EXTRACT(r in rs| {source:ID(startNode(r)) , target:ID(endNode(r))}) AS rels
Capture both paths, then build collections from the nodes and relationships of both paths. The collection of nodes actually only extracts the nodes from p1 and adds the 'd' node. You could write that part as
nodes(p1) + nodes(p2) as ns
but then the 'b' node will appear in the list twice.

Resources