Converting a star-topology to a linked list in Neo4j - linked-list

When I initially created my data set I thought a star topology would be nice, but now that I've gotten some sample data, I'd rather be using a linked list topology. Now I want to do an in-place conversion using Cypher only, and in one call. Is this possible?
To use a simplified example: a Blog node can have several children Post nodes, so I attached them directly like this:
CREATE (a:Blog), (a)-[:HAS]->(b:Post), (a)-[:HAS]->(c:Post)
RETURN a,b,c
Now as my design moves along, I think I'd rather have this as a linked list, something like this (assuming the :FIRST post is already created):
MATCH (a:Blog)-[r:FIRST]->(b:Post)
CREATE (a)-[:FIRST]->(c:Post)-[:PREV]->(b)
DELETE r
RETURN a,b,c
So I can code the linked list fine. What I need to do is to convert the old star topologies to the new linked list format. I tried a bunch of things but nothing worked, so here is my best guess, but is completely non-functional code:
MATCH (b:Blog)-[:HAS]-(p:Post)
WITH b, collect(p) as posts
CREATE b-[:FIRST]->posts[0]
FOREACH( i IN range(1,len(posts)-1) |
CREATE posts[i]-[:PREV]->posts[i-1]
)
RETURN b,p
Indexing collections doesn't seem to work in a CREATE statement (neither inside or outside the FOREACH).
Any way to achieve this translation?
EDIT
I thought an additional way might be to tag the posts with ordinal values and do some iteration over sequential ordinals, but I get a syntax error trying to add the ordinals:
MATCH (b:Blog)-[:HAS]-(p:Post)
WITH b, collect(p) as posts
FOREACH( i in range(0,length(posts)) |
SET posts[i].ordinal = i
}
The syntax error is:
Invalid input '[': expected an identifier character, node labels, a property map, a relationship pattern, '(', '.' or '=' (line 4, column 14)
" SET posts[i].ordinal = i"
^
EDIT/2
Another idea I had was to put the linked-list code inside the FOREACH, but this fails because MATCH cannot be used inside FOREACH. First I created and attached a dummy (b:Blog)-[:FIRST]->(p:Post) relationship, then wrote this query (which doesn't work):
MATCH (f)<-[:FIRST]-(b:Blog)-[:HAS]->(p:Post)
WITH b, collect(p) as posts
FOREACH( x IN posts |
MATCH (b)-[r:FIRST]->(f)
DELETE r
CREATE (b)-[r2:FIRST]->(x), (x)-[:PREV]->(f)
)
RETURN b-[*0..1]-()

As a crutch until you find a better workarund you could try nesting FOREACH (a IN [posts[i]] | FOREACH (b IN [posts[i-1]] | CREATE a-[:PREV]->b)) inside your original foreach loop, as a way to unpack the index slices. For edit 1 try SET (posts[i]).ordinal = i. I think I have a nicer workaround somewhere but can't remember at the moment, if I find it I'll put it up.

Try this Cypher Query
After Edit
MATCH (a:Blog)-[r:HAS]->(p:Post)
WITH a,r,collect(p) as posts,range(1,length(posts)) as rel
MERGE(firstPost:Post {created: head(posts).created , otherprop: head(posts).otherprop})
CREATE a-[:FIRST]->(firstPost)
FOREACH(i IN rel |
MERGE(thisPost:Post {created: (posts[i]).created , otherprop: (posts[i]).otherprop})
MERGE(prevPost:Post {created: (posts[i-1]).created , otherprop: (posts[i-1]).otherprop})
CREATE (prevPost)-[:NEXT]->(thisPost))
WITH a
MATCH a-[r:HAS]->() delete r
This will create something like (Blog)-[FIRST]->(Post)-[NEXT]->(Post)...
By default Cypher sorts the element in COLLECT(p) in natural ascending order. Hence if you have only created property it will sort in the most recent one at last, hence the last node in the next chain starting from BLOG will be your latest POST associated to that BLOG

Related

Sub queries in Cypher Query Language

I want to write a CREATE relationship statement for a project i'm working on. The statement has to be something like this
CREATE (match (p:Halt) where p.name="Ananda College" return p)-[:next_halt {route:['103'],dist:1.45}]->(MATCH (p:Halt) where p.name="Borella" return p)
As you can see i want the start node and end node to have values coming from another CQL statement.
But when i run this query there seems to be a syntax error. I have gone through some tutorials to see where my query is wrong but being a beginner i can't really tell.
Invalid input '(': expected whitespace, comment, node labels, MapLiteral, a parameter, ')' or a relationship pattern (line 1, column 15 (offset: 14))
"CREATE (match (p:Halt) where p.name="Ananda College" return p)-[:next_halt {route:['103'],dist:1.45}]->(MATCH (p:Halt) where p.name="Borella" return p)"
Your syntax is rather mixed up here. Please reread the dev documentation and maybe look at the Cypher cheat sheet.
As for proper syntax, you don't even need nesting to get what you want. First, you match on your start and end nodes, then you can use the bound variables in other parts of your query, such as to create a relationship:
MATCH (start:Halt), (stop:Halt)
WHERE start.name = "Ananda College" AND stop.name="Borella"
CREATE (start)-[:next_halt {route:['103'], dist:1.45}]->(stop)
If you aren't sure if nodes (or the relationship) exist or not, you can use MERGE instead, which will MATCH on existing nodes (or relationships) or create them if they do not exist.

How to delete a linkedlist in neo4j?

I have created a linked list structure for a feed scenario. Here i have my feeds persisted in the linked-list in neo4j. This is working fine. However, there might be a need to delete the whole product, so I am trying to come up with a cypher that can delete the product and the related linked list for feeds in one query.
This query returns me correct data
match p = (i:Item {uuid:"d98c299d-239b-40d1-ac2f-01a80ac36db4"})-[:LATEST]-(latestComment), latestComment-[:NEXT*0..]-(olders) return i, latestComment, olders;
based on these lines, I tried writing a delete query and its certain variations using foreach, but nothing seems to be working.
match p = (i:Item {uuid:"d98c299d-239b-40d1-ac2f-01a80ac36db4"})-[:LATEST]-(latestComment), latestComment-[:NEXT*0..]-(olders) delete i, latestComment, olders;
error:
Node record Node[13349,used=false,rel=26286,prop=-1,labels=Inline(0x0:[]),light] still has relationships
a variation with foreach
match (i:Item {uuid:"d98c299d-239b-40d1-ac2f-01a80ac36db4"})-[:LATEST]-(latestComment), latestComment-[:NEXT*0..]-(olders) delete i, foreach (old in olders | delete old)
clearly there is something wrong, but I am not getting enough pointers towards the possible error.
In order to delete a node, you must also delete all relationships touching that node. You queries do not do that.
In your case, since you want to delete the entire path, you might be able to simply do this:
MATCH p =(i:Item { uuid:"d98c299d-239b-40d1-ac2f-01a80ac36db4" })-[latest:LATEST]-(latestComment)-[:NEXT*0..]-(olders)
DELETE p;
However, if any node in the path has relationships that are not also in the path, then the above query need to be modified to include those extra relationships.

Keep track of Changes in Neo4j - Achieve functionality like a "flag" variable in standard programming

Initial setup of the sample database is provided link to console
There are various cases and within each case, there are performers(with properties id and name). This is the continuation of problems defined problem statement and solution to unique node creation
The solution in the second link is (credits to Christophe Willemsen
)
MATCH (n:Performer)
WITH collect(DISTINCT (n.name)) AS names
UNWIND names as name
MERGE (nn:NewUniqueNode {name:name})
WITH names
MATCH (c:Case)
MATCH (p1)-[r:RELATES_TO]->(p2)<-[:RELATES]-(c)-[:RELATES]->(p1)
WITH r
ORDER BY r.length
MATCH (nn1:NewUniqueNode {name:startNode(r).name})
MATCH (nn2:NewUniqueNode {name:endNode(r).name})
MERGE (nn1)-[rf:FINAL_RESULT]->(nn2)
SET rf.strength = CASE WHEN rf.strength IS NULL THEN r.value ELSE rf.strength + r.value END
This solution achieved what was asked for.
But I need to achieve something like this.
foreach (Case.id in the database)
{
foreach(distinct value of r.Length)
{
//update value property of node normal
normal.value=normal.value+0.5^(r.Length-2)
//create new nodes and add the result as their relationship or merge it to existing one
MATCH (nn1:NewUniqueNode {name:startNode(r).name})
MATCH (nn2:NewUniqueNode {name:endNode(r).name})
MERGE (nn1)-[rf:FINAL_RESULT]->(nn2)
//
rf.strength=rf.strength + r.value*0.5^(r.Length-2);
}
}
The problem is to track the change in the case and then the r.Length property. How can it be achieved in Cypher?
I will not redo the last part, where setting strengths.
One thing though, in your console link, there is only one Normal node, so why do you need to iterate over each case, you can just match distinct relationships lengths.
By the way for the first part :
MATCH (n:Case)
MATCH (n)-[:RELATES]->()-[r:RELATES_TO]->()<-[:RELATES]-(n)
WITH collect(DISTINCT (r.length)) AS lengths
MATCH (normal:Normal)
UNWIND lengths AS l
SET normal.value = normal.value +(0.5^l)
RETURN normal.value
Explanations :
Match the cases
Foreach case, match the RELATES_TO relationships of Performers for that Case
collect distinct lengths
Match the Normal node, iterate the the distinct lengths collection and setting the proper value on the normal node

Neo4J Arrays in MATCH query

The intention of my Query is to mark similar words.
CREATE CONSTRAINT ON (n:Word) ASSERT n.title IS UNIQUE
MATCH (n) WHERE ID(n)={id}
MERGE (o:Word{title:{title}})
WITH n,o MERGE n-[r:SIMILAR{location:'{location}'}]->o
RETURN ID(o)
n is a existing Word. I want to create the relationsship & the other Word (o) if they don't exist yet.
The Problem with this query is, that it works for one title, but if I use a Array with titles the title of the Word o is the whole Array.
Can you suggest me another Query that does the same and/or a way to pass multiple values to title.
I'm using the Neography Gem on Rails e.g. the REST API
To use individual values in a parameter array you can use FOREACH, something like
MATCH (n)
WHERE ID (n) = {id}
FOREACH (t IN {title} |
MERGE (o:Word {title:t})
MERGE n-[:SIMILAR]->o
)
If you want to pass location also as a parameter (it is actually a string literal in your current query), such that merge operations for n should happen for each title, location pair in a parameter array, you can try
FOREACH (map IN {maps} |
MERGE (o:Word {title:map.title})
MERGE n-[:SIMILAR {location:map.location}]->o
)
with a parameter that looks something like
{
"maps": [
{
"title":"neography",
"location":"1.."
},{
"title":"coreography",
"location":"3.."
}
]
}
Other suggestions:
It's usually not great to look up nodes by internal id from parameter. In some cases when chaining queries it may be fine, but in most cases label index lookup would be better: MATCH (n:Word {title:"geography"})
If you are not using the transactional cypher endpoint, give it a shot. You can then make one or more calls with one or more queries in each call within one transaction. Performance improves and you may find you don't need to send the more complex parameter object, but can send many simple queries.

Neo4j Cypher: Create a relation only if the end node exists

For the following Cypher statement:
start n=node:types(id={typeId}), g=node:groups(id={groupId})
create unique (n)<-[:has_type]-(unit {props})-[:has_group]->(g)
return unit
There are cases when g may be null (i.e. a group with id groupId does not exist).
In such a case, what should I do to make this statement still create the unit, but skip the has_group relation to g?
Right now, unit does not get created, presumably because g is null.
I'm using Neo4j Advanced 1.8
Thanks!
I would suggest to move the definition of g to the where clause, since starting at a non-existing node gives error and thus one can't continue the query to the create phase. Note the '?' which handles the null values in Cypher:
start n=node:types(id={typeId})
create unique (n)<-[:has_type]-(unit {props})-[:has_group]->(g)
where g.id?={groupId}
return unit
the query might need some tweaking, this is just my first untested shot.
edit: After some trying I came to a conclusion, that you might want to do 2 different queries, first for creating the first part of relationships with the unique node which is always and the second to create the relationship to the group which may not happen:
start n=node:types(id={typeId})
create unique (n)<-[:has_type]-(unit {props})
return unit
start unit=node:unitProps({unitPropsValue}) ,g=node:groups(id={groupId})
create unique unit-[:has_group]->g
return g
the second query will fail with an error in case the group does not exist, but that does not matter since you will still reach the target. For some strange reason I couldn't manage to implement some restrictions in the where clause like I tried in the first shot. following query seems to simply jump over the where conditions (maybe a bug?) although in my comprehension of Cypher it shall match the already existing group, but it does create a new g node instead:
start n=node(1)
create unique n-[:TYPE1]-(uniq {uid:333})
with uniq
create unique uniq-[:TYPE2]->g
where has(g.gid) and g.gid=999
return g
You can use WITH clause to achieve this in one query,
start n=node:types(id={typeId})
create unique (n)<-[:has_type]-(unit {props})
WITH unit
START g=node:groups(id={groupId})
create unique (unit)-[:has_group]->(g)
WHERE g <> null
return unit
if g is null, second won't get executed at all. even WHERE g <> null might not be required here. Kindly try and confirm
You can try this
MATCH (g:groups)
WHERE id(g)={groupId}
create unique (unit {props})-[:has_group]->(g)
WITH unit, create unique (unit)-[:has_type]->(n)
return unit
Since this is the only thing I can find related to this, I will add how I am dealing with this since none of the other answers are good enough for my purposes.
MATCH (a:TEST{id:1})
OPTIONAL MATCH (b:TEST)
WHERE b.id IN [2,3,5]
// collect b so that there are no nulls, and rows aren't lost when no match
WITH a, collect(b) AS c
FOREACH(n IN c | CREATE UNIQUE (a)-[:HAS]->(n))
RETURN a
Will work for Cypher 2.3+ (I can't test any earlier than that)
For those with APOC, you can also use CALL apoc.cypher.doIt() to break out the write.

Resources