Consider this toy example:
MERGE (:Obj {desc:'A',id:1})
MERGE (:Obj {desc:'A',id:2})
MERGE (:Obj {desc:'A',id:3})
MERGE (:Obj {desc:'A',id:4})
MERGE (:Obj {desc:'B',id:5})
MERGE (:Obj {desc:'B',id:6})
I have 6 different nodes, but there are only two different values for the description (so, only two classes of nodes). Suppose I want to create now a relationship SAME_AS through which all nodes having the same description will be connected. That is, that I should be able to get from a node x to all other nodes having the same description traversing SAME_AS relationships.
For the connected subcomponents I would prefer star-like graphs, where for each category the node with the smallest id acts as prototype to which all other equivalent nodes link to. (Another possibility, not as good, would be Hamiltonian paths)
So far, I only came up with this
MATCH (o1:Obj), (o2:Obj)
WHERE o1.desc=o2.desc AND o1.id<o2.id
MERGE (o1)<-[:SAME_AS]-(o2)
But it builds a clique for each class.
Better solutions required using a MATCH within a FOREACH statement, which isn't allowed in neo4j.
I don't even get whether cypher is appropriated to do this, or whether I should perform these calculations outside neo4j and only afterwards write the info within the DB.
Thanks
How about something like this
// match the objects
MATCH (o1:Obj)
WITH o1.desc AS desc, o1
// put them in ascending order so we can find the smallest
ORDER BY desc,o1.id
// collect them per description
WITH desc, collect(o1) AS objs
// connect 1..n to node 0
UNWIND RANGE(1,size(objs)-1) AS idx
WITH objs[0] AS origin, objs[idx] AS dupe
MERGE (origin)<-[:SAME_AS]-(dupe)
Related
I am a newbie who just started learning graph database and I have a problem querying the relationships between nodes.
My graph is like this:
There are multiple relationships from one node to another, and the IDs of these relationships are different.
How to find relationships where the number of relationships between two nodes is greater than 2,or is there a problem with the model of this graph?
Just like on the graph, I want to query node d and node a.
I tried to use the following statement, but the result is incorrect:
match (from)-[r:INVITE]->(to)
with from, count(r) as ref
where ref >2
return from
It seems to count the number of relations issued by all from, not the relationship between from-->to.
to return nodes who have more then 2 relationship between them you need to check the size of the collected rels. something like
MATCH (x:Person)-[r:INVITE]-(:Party)
WITH x, size(collect(r)) as inviteCount
WHERE inviteCount > 2
RETURN x
Aggregating functions like COLLECT and COUNT use non-aggregating terms in the same WITH (or RETURN) clause as "grouping keys".
So, here is one way to get pairs of nodes that have more than 2 INVITE relationships (in a specific direction) between them:
MATCH (from)-[r:INVITE]->(to)
WITH from, to, COUNT(r) AS ref
WHERE ref > 2
RETURN from, to
NOTE: Ideally (for clarity and efficiency), your nodes would have specific labels and the MATCH pattern would specify those labels.
I have 2 Account Nodes, and several listing nodes, as seen below. The Match statement results in 2 Accounts being show with a relationship to each Listing thats associated to that account.
What im wanting to do is create a relationship between the two Accounts based on at least 1 of there listings each sharing the same phone number.
If possible im wanting to see the relationship between the two account nodes drawn and a relationship between the two listings so long as the listings are from different Account.
MERGE (:Account {account_id:11})
MERGE (:Listing {account_id:11, listing_id:1001, phone:99468320, author:'Paul'})
MERGE (:Account {account_id:12})
MERGE (:Listing {account_id:12, listing_id:1002, phone:97412521, author:'Sam'})
MERGE (:Listing {account_id:12, listing_id:1003, phone:97412521, author:'Sam'})
MERGE (:Listing {account_id:12, listing_id:1004, phone:99468320, author:'Sam'})
MERGE (:Listing {account_id:12, listing_id:1004, phone:0, author:'Same'})
MATCH (a:Account),(l:Listing)
WHERE a.account_id = l.account_id
CREATE (a)-[:LISTING]->(l)
RETURN a,l;
For the latter i did try the following but it went a bit crazy as it linked every listing to each other that had the same number appose to only doing so if the account_id was different.
match (p1:Listing)
with p1
match (p2:Listing)
where p2.phone = p1.phone and p1 <> p2
merge(p1)-[r:SHARED_PHONE]-(p2)
RETURN p1, p2
First of all, you should carefully consider if you really need the SHARED_PHONE relationships, as you will have to update the relationships every time a phone number is added, removed, or changed. That could complicate a lot of your queries and make your DB unnecessarily slower. Also, you could end up with a lot of SHARED_PHONE relationships (that you may not really need). Instead of creating the relationships, you could consider incorporating into the relevant queries the discovery of the nodes with the same phone numbers.
However, if you decide that you really need that relationship, here is one way to do what you want:
[UPDATED]
MATCH (n: Listing)
WITH n.phone AS phone, COLLECT(n) AS ns
FOREACH(i IN RANGE(0, SIZE(ns)-2) |
FOREACH(x IN [ns[i]] |
FOREACH(y IN [z IN ns[i+1..] WHERE x.account_id <> z.account_id] |
MERGE (x)-[:SHARED_PHONE]-(y)
)))
The WITH clause collects all the (unique) Listing nodes that share the same phone, and the nested FOREACH clauses execute the minimum number of MERGEs needed to ensure that all the appropriate nodes are connected by a SHARED_PHONE relationship (in either direction). The innermost FOREACH also ensures that the nodes to be connected do not have the same account_id.
I have different nodes that share one same property field, i need to merge these nodes into one and in the same time copy all the rest of the other properties in the merge node.
example:
(n1,g,p1) (n2,g,p2) (n3,g,p3) =>(n,g,p1,p2,p3)
Important to Note that i don't need the apoc solutions since user defined functions dosen't work in CAPS that i m working at
update :
geohash is the field that have a repeated values, so i want to merge the nodes by this field .
The CAPS team gave me this cypher query to have distinct geohash nodes from the intial graph:
CATALOG CREATE GRAPH temp {
FROM GRAPH session.inputGraph
MATCH (n)
WITH DISTINCT n.geohash AS geohash
CONSTRUCT
CREATE (:HashNode {geohash: geohash})
RETURN GRAPH
}
, however it still missing is the collect of the rest of the properties on the merged nodes.
I haven't a problem for the relationship ,cause we can copy them later from the intial graph:
FROM GRAPH inputGraph
MATCH (from)-[via]->(to)
FROM GRAPH temp
MATCH (n), (m)
WHERE from.geohash = n. AND AND to.geohash = m.geohash
CONSTRUCT
CREATE (n)-[COPY OF via]->(m)
RETURN GRAPH
It's not 100% possible in pure cypher, that's why there is an APOC procedure for that.
To merge two nodes , you have to :
create the merge node with all the properties
to create all the relationship of the nodes on the merge one
For the first part it's possible in cypher. Example :
MATCH (n) WHERE id(n) IN [106, 68]
WITH collect(n) AS nodes
CREATE (new:MyNode)
with nodes, new
UNWIND nodes as node
SET new += properties(node)
RETURN new
But for the second part, you need to be able to create relationship with a dynamic type and dynamic direction, and this is not allowed in cypher ...
How can we add a relationship to the query.
Say A-[C01]-B-[C02]-D and A-[C01]-B-[C03]-E
C01 C02 C03 are relationship codes I want to get output
B E
because I want only nodes that can be reached unbroken by C01 or C03
How can I get this result in Cypher?
You may want to clarify, what you're asking for seems like a very simple case of matching. You may want to provide some more info, such as node labels and how you're matching to your start nodes, since without these we have to make things up for example code.
MATCH (a:Thing)
WHERE a.ID = 123
WITH a
MATCH (a)-[:C01|C03*]->(b:Thing)
RETURN b
The key here is specifying multiple relationship types to traverse, using * for multiplicity, so it will match on all nodes that can be reached by any chain of those relationships.
I'm modeling a "tag cloud" with the graph:
(t:Tag {name:'cypher'})-[:IN]->(g:TagGroup)<-[:TAGGED]-(x)
IE: A named tag is part of a "TagGroup", to which zero or more nodes are "TAGGED". I chose this design as I want the ability to combine two or more named tags (e.g. "cypher" and "neo4j") so that both (Tag)s are [IN]the new (TagGroup) and the new (TagGroup) is the endpoint for the union of all nodes that were previously [TAGGED].
My only (not very pleasing) attempt is:
match (t:Tag {name:'cypher'})-[i:IN]->(g:TagGroup),
(t2:Tag {name:'neo4j'})-[:IN]->(g2:TagGroup)<-[y:TAGGED]-(x)
create (t2)-[:IN]->(g)
create unique (g)<-[:TAGGED]-(x)
with g2 as g2
match (g2)<-[r]->() delete g2,r
My main issues is that it only combines two nodes, and doesn't feel very efficient (although I have no alternatives to compare it with). Ideally I'd be able to combine an arbitrary set of (Tag)s by name.
Any ideas if this can be done with Cypher, and if so, how?
You can use labels instead of creating separate tag groups.
eg. if tag neo4j and cypher come under tag group say XYZ then
MERGE (a:Tag {name: "neo4j"})-[:TAGGED]->(x)
MERGE (b:Tag {name: "cypher"})-[:TAGGED]->(x)
set a :XYZ , b :XYZ
So next time you want tags of a particular group TAGGED to a particular post x
MATCH (a:Tag:XYZ)-[:TAGGED]->(x) return a.name