I have a schema where Product contains many revisions ProductRevision which say in which status the product is and it is connected to specific ProductParts configuration with nodes for each Part. Each Part can be used in many ProductParts and each ProductParts is used usually in three revisions (status New, Tested and Production). If ProductParts is changed, a new revision is created with status New and connected with previons last revision.
I have there also shortcuts like relationships LATEST, LATEST_NEW, LATEST_TESTED and LATEST_PRODUCTION which connect Product directly with latest node of revision, latest node of revision in status New, etc.
Command for creating simple example is here
CREATE (p:Product {Name:"Test1"})-[:REVISION {Created:datetime()}]->
(pr1:ProductRevision {Status:"New", Created:datetime()})-[:USING]->(pp:ProductParts)
CREATE (pp)-[:CONTAINS]->(p1:Part {Id:1})
CREATE (pp)-[:CONTAINS]->(p2:Part {Id:2})
CREATE (pr1)-[:REVISION {Created:datetime()}]->(pr2:ProductRevision {Status:"Tested", Created:datetime()})-[:USING]->(pp)
CREATE (pr2)-[:REVISION {Created:datetime()}]->(pr3:ProductRevision {Status:"Production", Created:datetime()})-[:USING]->(pp)
CREATE (ppChanged:ProductParts)
CREATE (ppChanged)-[:USING]->(p1)
CREATE (ppChanged)-[:USING]->(p3:Part {Id:3})
CREATE (pr3)-[:REVISION {Created:datetime()}]->(pr4:ProductRevision {Status:"New", Created:datetime()})-[:USING]->(ppChanged)
CREATE (pr4)-[:REVISION {Created:datetime()}]->(pr5:ProductRevision {Status:"Tested", Created:datetime()})-[:USING]->(ppChanged)
CREATE (p)-[:LATEST {Created:datetime()}]->(pr5)
CREATE (p)-[:LATEST_NEW {Created:datetime()}]->(pr4)
CREATE (p)-[:LATEST_TESTED {Created:datetime()}]->(pr5)
CREATE (p)-[:LATEST_PRODUCTION {Created:datetime()}]->(pr3)
So and I need to get chain like (Product)-(ProductParts)-(Part) where ProductParts is connected through latest revision in specific status, e.g. Tested or Production.
I wanted to use shortcuts relationships LATEST_* but it doesn't work as I expected. I tried query like
MATCH (p:Product)-[:LATEST_TESTED|LATEST_PRODUCTION]-(pr:ProductRevision)--(pp:ProductParts)--(pa:Part)
WITH *, max(pr.Created) as prc
RETURN p,pr,prc,pp,pa
but it returns all ProductParts with specific status not only the last one.
If I don't return pr relation between Product and ProductParts missing.
Exists any way how to get following result when I want latest revision with specific status Tested or Production
(:Product {Name: "Test"}) --> (:ProductRevision {Status:"Tested"}) --> (:ProductParts) --> (:Part {Id:1})
. \-> (:Part {Id:3})
You have to split your request in two parts:
- First, get the latest ProductRevision you are interested in
- Second, get all ProductParts and Parts related to the ProductRevision
MATCH (p:Product)-[:LATEST_TESTED|LATEST_PRODUCTION]-(pr:ProductRevision)
WITH p, pr
ORDER BY pr.Created DESC
LIMIT 1
MATCH (pr)--(pp:ProductParts)--(pa:Part)
RETURN p, pr, pp, pa
The first MATCH gets the latest ProductRevision by ordering the results descending and keeping only the first result
The second MATCH gets the rest of the information you need.
Related
I just downloaded and installed Neo4J. Now I'm working with a simple csv that is looking like that:
So first I'm using this to merge the nodes for that file:
LOAD CSV WITH HEADERS FROM 'file:///Athletes.csv' AS line
MERGE(Rank:rank{rang: line.Rank})
MERGE(Name:name{nom: line.Name})
MERGE(Sport:sport{sport: line.Sport})
MERGE(Nation:nation{pays: line.Nation})
MERGE(Gender: gender{genre: line.Gender})
MERGE(BirthDate:birthDate{dateDeNaissance: line.BirthDate})
MERGE(BirthPlace: birthplace{lieuDeNaissance: line.BirthPlace})
MERGE(Height: height{taille: line.Height})
MERGE(Pay: pay{salaire: line.Pay})
and this to create some constraint for that file:
CREATE CONSTRAINT ON(name:Name) ASSERT name.nom IS UNIQUE
CREATE CONSTRAINT ON(rank:Rank) ASSERT rank.rang IS UNIQUE
Then I want to display to which country the athletes live to. For that I use:
Create(name)-[:WORK_AT]->(nation)
But I have have that appear:
I would like to know why I have that please.
I thank in advance anyone that takes time to help me.
Several issues come to mind:
If your CREATE clause is part of your first query: since the CREATE clause uses the variable names name and nation, and your MERGE clauses use Name and Nation (which have different casing) -- the CREATE clause would just create new nodes instead of using the Name and Nation nodes.
If your CREATE clause is NOT part of your first query: your CREATE clause would just create new nodes (since variable names, even assuming they had the same casing, are local to a query and are not stored in the DB).
Solution: You can add this clause to the end of the first query:
CREATE (Name)-[:WORK_AT]->(Nation)
Yes, Agree with #cybersam, it's the case sensitive issue of 'name' and 'nation' variables.
My suggesttion:
MERGE (Name)-[:WORK_AT]->(Nation)
I see that you're using MERGE for nodes, so just in case any values of Name or Nation duplicated, you should use MERGE instead of CREATE.
I am trying to achieve what is shown here:
I have 2 CSV Files, diease_mstr and Test_mstr Now in Test_mstr, I have many test to disease ID records, which means none of them are unique. The disease ID points to disease_mstr file. In disease_mstr file I have only 2 fields, ID and Disease_name (disease name is unique).
Now, I am creating 3 nodes with labels
1) Tests (only "testname" property) which will have unique tests (total 345 unique testnames)
**Properties :**
a) testname
2) Linknode (pulled entire Test_mstr file) also pulled "disease_name" for corresponding disease_ID from Disease_mstr File
**Properties**
a)tname
b)dname
c)did
3) Disease (pulled form disease_mstr) file.
**Properties**
a)did
b)diseasename
Afterwhich I run create relationships
1)MATCH (t:Tests),(n:Linknode) where t.testname = n.tname CREATE (n)-[r:TEST_2]->(t) RETURN n,r,t
2)MATCH (d:Disease), (l:Linknode) where d.did = l.did MERGE (d)-[r:FOR_DISEASE]->(l) RETURN d,r,l
To get the desired result as shown in image, I run following cypher command :
MATCH (d:Disease)-[r2:FOR_DISEASE]->(l:Linknode)-[r:TEST_2]->(t:Tests) RETURN l,r,t,r2 LIMIT 25
Can someone please help me create 2 more relationships which is marked and linked in image with BLUE and GREEN lines?.
Sample files and images can be accessed in my google folder link
Is your goal to link all diseases to tests so that for any disease you can find out which tests are relevant and for each test, which diseases it tests for?
If so, you are nearly there.
You don't need the link nodes other than to help you during linking the tests to the diseases. In your current scenario you're treating the link nodes as you would if you were creating a relational database. They won't add any value in your graph db. You can create a single relationship between diseases and tests which will do all the work.
Here's a step by step way to load your database. (It probably isn't the most efficient, but it's easy to follow and it works.)
Normalise and load your tests:
load csv with headers from "file:///test_mstr_csv.csv" as line
merge (:Test {testname:line.test_name});
Load your diseases (these looked normalised to me)
load csv with headers from "file:///disease_mstr_csv.csv" as line
create (:Disease {did:line.did, diseasename:line.disease_name});
Load your link nodes:
load csv with headers from "file:///test_mstr_csv.csv" as line
merge (:Link {testname:line.test_name, parentdiseaseid:line.parent_disease_ID});
Now you can create a direct relationship between the diseases and tests with the following query:
match(d:Disease), (l:Link) where d.did = l.parentdiseaseid
with d, l.testname as name
match(t:Test {testname:name}) create (d)<-[:TEST_FOR]-(t);
This last query will find all the link nodes for each disease and extract the test name. It then looks up the test and joins it directly to its corresponding disease.
The link nodes are redundent now, so you can delete them if you wish.
To create the 'blue lines', which I assume are meant to show where tests have diseases in common, run the query below:
match (d:Disease)<-[]-(:Test)-[]->(e:Disease) where id(d) > id(e)
merge (d)-[:BLUE_LINE]->(e);
The match clause finds all disease pairs with a common test, the where clause ensures a link is created in only one direction and the merge clause ensures only one link is created.
I trying to set up a scheme for web-clicks, where each node is a (:Click), which links to the click that precedes it by a [:PREV]-edge and the (:Session) that owns it by a [:GEN]-edge. In the end this should happen procedural, a new transaction/insert when a new click is made. While I have no problem generating the involved objects, I cannot figure out how to dynamically select last (:Click) and link it to the current created one.
Generate a session with 2 clicks:
CREATE (s:Session {name:'S0'})
CREATE (c1:Click {name:'C1', click:1}), (c1)<-[:GEN]-(s)
CREATE (c2:Click {name:'C2', click:2}), (c2)<-[:GEN]-(s), (c1)<-[:PREV]-(c2);
generate one other click in separated transaction:
MERGE (s:Session {name:'S0'})
CREATE (c3:Click {name:'C3', click:3}),
(c3)<-[:GEN]-(s) //(c2)<-[:PREV]-(c3);
for the commented out link, I cannot use the c2-variable as it is scope-local to the previous transaction.
Now I thought to try something like this to dynamically find the last generated node on the same session and link it
MERGE (s:Session {name:'S0'})
CREATE (c3:Click {name:'C3', click:3}), (c3)<-[:GEN]-(s)
MATCH (s)-[:GEN]->(c_prevs:Click)
WITH c_prevs
ORDER BY c_prevs.click DESC LIMIT 1
CREATE (head(c_prevs))<-[:PREV]-(c3)
Unfortunately this won't work for me with any Cypher-construct I came up with so far.
If I understand you can get the last :Click node on the same session this way:
match (:Session {name:'S0'})-[:GEN]->(c:Click)
where not (:Click)-[:PREV]->(c)
return c
That is: Get the node from the same session that does not have an incoming [PREV] relationship. Will return c2
╒═══════════════════════╕
│"c" │
╞═══════════════════════╡
│{"name":"C2","click":2}│
└───────────────────────┘
For your specific case a query like the following should work:
merge (s:Session {name:'S0'})
with s
match (s)-[:GEN]->(last:Click)
where not (:Click)-[:PREV]->(last)
create (c3:Click {name:'C3', click:3}),
(c3)<-[:GEN]-(s),
(last)<-[:PREV]-(c3)
I found the answer to my question to be the following
MATCH (s:Session {name:'S0'})
CREATE (c3:Click {name:'C3', click:3})
WITH s, c3
MATCH (s)-[:GEN]->(c_prev:Click)
WITH c_prev, c3, s
ORDER BY c_prev.click DESC LIMIT 1
WITH c_prev, c3, s
CREATE (c_prev)<-[:PREV]-(c3), (c3)<-[:GEN]-(s)
which is chaining through the nodes as variables s, c3 and last_c with the WITH keyword. Unfortunately this involves a lot of repetition, as every WITH in principle is a part-separator in the query, so I learned.
This also allows to carry over already MERGED/CREATED nodes, which might help to ensure their existence.
EDIT:
This problem seems to be even more complicated if clicks should be generated prozedural, thus using one cypher-statement to insert and link any click.
my solution looks like the following
MERGE (s:Session {name: $session_name})
WITH s
CREATE (c:Click {name: $click_name, click: $click_count})
WITH s, c
OPTIONAL MATCH (s)-[:GEN]->(c_prev:Click)
WITH c_prev, c, s
ORDER BY c_prev.click DESC LIMIT 1
WITH c_prev, c, s
FOREACH (o IN CASE WHEN c_prev IS NOT NULL THEN ['1'] ELSE [] END |
CREATE (c_prev)<-[:PREV]-(c)
)
WITH s, c
CREATE (c)<-[:GEN]-(s)
with executing this statement for {$session_name, $click_name, $click_count} =[{'AAA', 'C1', 1}, {'AAA', 'C2', 2}, {'AAA', 'C3', 3}].
Notice that I had to work around the returning empty node-list by explicitly catching this condition and then not executing the subsequent connection statement with the FOREACH-loop on an empty list. This does not only look very ugly, I sincerely think there should be a better way to expressively specify this desired behavior through Cypher in the near future.
i'm trying to solve a problem of the 1: many relationship display in neo4j. My dataset is as below
child,desc,type,parent
1,PGD,Exchange,0
2,MSE 1,MSE,1
3,MSE 2,MSE,1
4,MSE 3,MSE,1
5,MSE 4,MSE,1
6,BRAS 1,BRAS,2
6,BRAS 1,BRAS,3
7,BRAS 2,BRAS,4
7,BRAS 2,BRAS,5
10,NPE 1,NPE,6
11,NPE 2,NPE,7
12,OLT,OLT,10
12,OLT,OLT,11
13,FDC,FDC,12
14,FDP,FDP,13
15,Cust 1,Customer,14
16,Cust 2,Customer,14
17,Cust 3,Customer,14
LOAD CSV WITH HEADERS FROM 'file:///FTTH_sample.csv' AS line
CREATE(:ftthsample
{child_id:line.child,
desc:line.desc,
type:line.type,
parent_id:line.parent});
//Relations
match (child:ftthsample),(parent:ftthsample)
where child.child_id=parent.parent_id
create (child)-[:test]->(parent)
//Query:
MATCH (child)-[childrel:test*]-(elem)-[parentrel:test*]->(parent)
WHERE elem.desc='FDP'
RETURN child,childrel,elem,parentrel
It returns a display as below.
I want the duplicate nodes to be displayed as one. Newbie with Neo4J. Can anyone of the experts help please?
This seems like an error in your graph creation query. You have a few lines in your query specifying the same node multiple times, but with multiple parents:
6,BRAS 1,BRAS,2
6,BRAS 1,BRAS,3
I'm guessing you actually want this to be a single node, with parent relationships to nodes with the given parent ids, instead of two separate nodes.
Let's adjust your import query. Instead of using a CREATE on each line, we'll use MERGE, and just on the child_id, which seems to be your primary key (maybe consider just using id instead, as a node can have an id on its own, without having to consider the context of whether it's a parent or child). We can use the ON CREATE clause after MERGE to add in the remaining properties only if the MERGE resulted in node creation (instead of matching to an existing node.
That will ensure we only have one node created per child_id.
Rather than having to rematch the child, we can use the child node we just created, match on the parent, and create the relationship.
LOAD CSV WITH HEADERS FROM 'file:///FTTH_sample.csv' AS line
MERGE(child:ftthsample {child_id:line.child})
ON CREATE SET
child.desc = line.desc,
child.type = line.type
WITH child, line.parent as parentId
MATCH (parent:ftthsample)
WHERE parent.child_id = parentId
MERGE (child)-[:test]->(parent)
Note that we haven't added line.parent as a property. It's not needed, since we only use that to create relationships, and after the relationships are there, we won't need those again.
I am trying to create a social network-like structure.
I would like to create a timeline of posts which looks like this
(user:Person)-[:POSTED]->(p1:POST)-[:PREV]->[p2:POST]...
My problem is the following.
Assuming a post for a user already exists, I can create a new post by executing the following cypher query
MATCH (user:Person {id:#id})-[rel:POSTED]->(prev_post:POST)
DELETE rel
CREATE (user)-[:POSTED]->(post:POST {post:"#post", created:timestamp()}),
(post)-[:PREV]->(prev_post);
Assuming, the user has not created a post yet, this query fails. So I tried to somehow include both cases (user has no posts / user has at least one post) in one update query (I would like to insert a new post in the "post timeline")
MATCH (user:Person {id:"#id"})
OPTIONAL MATCH (user)-[rel:POSTED]->(prev_post:POST)
CREATE (post:POST {post:"#post2", created:timestamp()})
FOREACH (o IN CASE WHEN rel IS NOT NULL THEN [rel] ELSE [] END |
DELETE rel
)
FOREACH (o IN CASE WHEN prev_post IS NOT NULL THEN [prev_post] ELSE [] END |
CREATE (post)-[:PREV]->(o)
)
MERGE (user)-[:POSTED]->(post)
Is there any kind of if-statement (or some type of CREATE IF NOT NULL) to avoid using a foreach loop two times (the query looks a litte bit complicated and I know that the loop will only run 1 time)?.
However, this was the only solution, I could come up with after studying this SO post. I read in an older post that there is no such thing as an if-statement.
EDIT: The question is: Is it even good to include both cases in one query since I know that the "no-post case" will only occur once and that all other cases are "at least one post"?
Cheers
I've seen a solution to cases like this in some articles. To use a single query for all cases, you could create a special terminating node for the list of posts. A person with no posts would be like:
(:Person)-[:POSTED]->(:PostListEnd)
Now in all cases you can run the query:
MATCH (user:Person {id:#id})-[rel:POSTED]->(prev_post)
DELETE rel
CREATE (user)-[:POSTED]->(post:POST {post:"#post", created:timestamp()}),
(post)-[:PREV]->(prev_post);
Note that the no label is specified for prev_post, so it can match either (:POST) or (:PostListEnd).
After running the query, a person with 1 post will be like:
(:Person)-[:POSTED]->(:POST)-[:PREV]->(:PostListEnd)
Since the PostListEnd node has no info of its own, you can have the same one node for all your users.
I also do not see a better solution than using FOREACH.
However, I think I can make your query a bit more efficient. My solution essentially merges the 2 FOREACH tests into 1, since prev_postand rel must either be both NULL or both non-NULL. It also combines the CREATE and the MERGE (which should have been a CREATE, anyway).
MATCH (user:Person {id:"#id"})
OPTIONAL MATCH (user)-[rel:POSTED]->(prev_post:POST)
CREATE (user)-[:POSTED]->(post:POST {post:"#post2", created:timestamp()})
FOREACH (o IN CASE WHEN prev_post IS NOT NULL THEN [prev_post] ELSE [] END |
DELETE rel
CREATE (post)-[:PREV]->(o)
)
In the Neo4j v3.2 developer manual it specifies how you can create essentially a composite key made of multiple node properties at this link:
CREATE CONSTRAINT ON (n:Person) ASSERT (n.firstname, n.surname) IS NODE KEY
However, this is only available for the Enterprise Edition, not Community.
"CASE" is as close to an if-statement as you're going to get, I think.
The FOREACH probably isn't so bad given that you're likely limited in scope. But I see no particular downside to separating the query into two, especially to keep it readable and given the operations are fairly small.
Just my two cents.