I have some relations between persons in my graph.
my data (generate script below)
create (s:Person {name: "SUE"})
create(d:Person {name: "DAVID"})
create(j:Person {name: "JACK"})
create(m:Person {name: "MARY"})
create(js:Person {name: "JASON"})
create(b:Person {name: "BOB"})
create(a1:Adress {id:1})
create(a2:Adress {id:2})
create(a3:Adress {id:3})
create(a4:Adress {id:4})
create(a5:Adress {id:5})
merge (d)-[:MOTHER]->(s)
merge(j)-[:MOTHER]->(s)
merge(js)-[:MOTHER]->(m)
merge(b)-[:MOTHER]->(m)
merge(b)-[:CURRENT_ADRESS]->(a1)
merge(js)-[:CURRENT_ADRESS]->(a2)
merge(j)-[:CURRENT_ADRESS]->(a3)
merge(s)-[:CURRENT_ADRESS]->(a4)
merge(d)-[:CURRENT_ADRESS]->(a5)
;
I can get mothers who live with her child:
MATCH (p:Person)-[:CURRENT_ADRESS]->(a:Adress)<-[:CURRENT_ADRESS]-(t), (t)-[:MOTHER]->(p)
return p.name,t.name
p.name t.name
MARY JASON
but i want to get mothers who is not living with any child of her.
How can i do that in Cyper?
Actually in your graph, everybody is living at a different address due to different identifiers.
Let's build a graph example introducing the sister which lives at the same address :
CREATE
(p:Person)-[:MOTHER]->(m:Person),
(p)-[:FATHER]->(f:Person),
(p)-[:SISTER]->(s:Person),
(p)-[:CURRENT_ADDRESS]->(a:Adress),
(m)-[:CURRENT_ADDRESS]->(b:Adress),
(f)-[:CURRENT_ADDRESS]->(c:Adress),
(s)-[:CURRENT_ADDRESS]->(a)
Now this is very simple, match family members that don't have a CURRENT_ADDRESS relationship in depth2 to the family member :
MATCH (p:Person)-[:MOTHER|:FATHER|:SISTER]->(familyMember)
WHERE NOT EXISTS((p)-[:CURRENT_ADDRESS*2]-(familyMember))
RETURN familyMember
Try this
MATCH (p:Person)-[:CURRENT_ADRESS]-(a:Adress), (p)-[:MOTHER|:FATHER]->(t)
WITH p,a,t
MATCH (p), (t) where not (t)-[:CURRENT_ADRESS]-(a)
return p.NAME,t.NAME
This should work:
MATCH (p:Person)-[:CURRENT_ADRESS]-(a:Adress), (p)-[:MOTHER|:FATHER]->(t)-[:CURRENT_ADRESS]-(b:Adress)
WHERE a <> b
return p.NAME, t.NAME;
By the way, I'd also put the appropriate direction arrow on the CURRENT_ADRESS relationships.
Finally i found it.
match path=(p:Person)-[:MOTHER]->(m:Person)-[:CURRENT_ADRESS]->(a:Adress)
where all(x in nodes(path) where not exists((p)-[:CURRENT_ADRESS]->(a)))
return path
Related
I have one CQL syntax, and I don't understand how to add relationship id to another relationship within a single query. This is the sample that might give clearer description about what I need
CREATE
(a:IP_ADDRESS {name: "224.36.71.118"}),
(b:TARGET {honeypot: "cowrie", target_post: "2222", target_protocol: "tcp", analyzer_id: "VM1"}),
(c:Unknown {name: 'uname -a;lspci', threat_phase:"Unknown", threat_purpose: "Unknown", threat_category: "Unknown", time: "2022-07-21T00:02:03+0000"}),
(a)-[r:ATTACK {time: "2022-08-21T19:51:05+0000"}]->(b),
(b)-[:GO_TO {attack_id: r.id}]->(c)
ASo, what I'm trying to achieve is, I want to insert the relationship id from (a)->(b) into the relationship from (b)->(c). I already tries to use r.id as the relationship data, but it doesn't work. The query doesn't return error, but the (b)->(c) relationship didn't successfully stored the attack_id
To get the value of the object id of a node or an edge, you will use id(r) and NOT r.id. The property id is a user-defined property so you should define it while id(r) is a function created by neo4j to get the object id of r. Below is the correct query;
CREATE
(a:IP_ADDRESS {name: "224.36.71.118"}),
(b:TARGET {honeypot: "cowrie", target_post: "2222", target_protocol: "tcp", analyzer_id: "VM1"}),
(c:Unknown {name: 'uname -a;lspci', threat_phase:"Unknown", threat_purpose: "Unknown", threat_category: "Unknown", time: "2022-07-21T00:02:03+0000"}),
(a)-[r:ATTACK {time: "2022-08-21T19:51:05+0000"}]->(b),
(b)-[:GO_TO {attack_id: id(r)}]->(c)
Result:
If you use separate CREATE statements, it works perfectly. Try this
CREATE (t1:Test),
(t2:Test),
(t3:Test)
CREATE (t1)-[r:REL_TO]->(t2)
CREATE (t2)-[r2:REL_TO {idr: id(r)}]->(t3)
RETURN id(r), properties(r2)
which returns
╒═══════╤════════════════╕
│"id(r)"│"properties(r2)"│
╞═══════╪════════════════╡
│12 │{"idr":12} │
└───────┴────────────────┘
How do I return just the portion of a variable length pattern path that meets some criteria?
In the example below, the following cypher query will return both blue & red nodes.
MATCH p=(a:Person)-[:KNOWS*1..10]->(b:Person)-[:KNOWS]->(j:Person { name: 'Jane'})
RETURN p
However, I want to return the blue nodes that have an incoming relationship confidence_factor >= 0.75. The issue is that everything I've tried either
Eliminates the entire upper mixed blue/red path b/c it's got one rel that fails test
Eliminates just the Jim->Erin rel b/c it fails the test.
Effectively, I want all sequential nodes going backwards from Jane where relationship confidence_factor >= 0.75 but a given path should stop as soon as it encounters a rel that fails that test, and NOT CONTINUE, even if other relationships between nodes in that path might pass (e.g. Tom-[:KNOWS]->Jim)
CREATE (one:Person { name: 'Tom'})
,(two:Person { name: 'Jim'})
,(three:Person { name: 'Erin'})
,(four:Person { name: 'Kevin'})
,(five:Person { name: 'Skylar'})
,(six:Person { name: 'Jane'})
,(one)-[:KNOWS {confidence_factor:0.80}]->(two)
,(two)-[:KNOWS {confidence_factor:0.05}]->(three)
,(three)-[:KNOWS {confidence_factor:0.85}]->(six)
,(four)-[:KNOWS {confidence_factor:0.90}]->(five)
,(five)-[:KNOWS {confidence_factor:0.95}]->(six)
;
You should already get multiple rows with different path lengths. Just look for those paths where all relationships match your criteria.
MATCH p = (:Person {name: 'Jane'})<-[:KNOWS*1..10]-(:Person)
WHERE all(r IN relationships(p) WHERE r.confidence_factor >= 0.75)
RETURN p
If you want to extract the nodes from the path without Jane (remove tail() if you want Jane to be included):
MATCH p = (:Person { name: 'Jane'})<-[:KNOWS*1..10]-(:Person)
WHERE all(r IN relationships(p) WHERE r.confidence_factor >= 0.75)
UNWIND tail(nodes(p)) as person
RETURN DISTINCT person
This question is a direct extension of a question I asked previously here (and and even earlier version here).
Say I have a graph database that looks like this:
Just like the previous questions I asked, the only really interesting thing about this is that SomeProperty can be 'Yes' or 'No'.
In the top row, 1 of the three nodes has a 'Yes' for this property.
On the bottom row, 3 nodes of the five nodes have a 'Yes' for this property.
(Slight philosophical sidenote: I'm starting to suspect that this is a bad graph schema. Why? Because, within each set of nodes, each node is in connected to every other node. I'm not worried about the fact that there are two groups of nodes, but the fact that when I populate this graph, I get talkback that says, 'Returned 530 rows.' I think this means actually created 530 subpaths within the graph structure and this seems like overkill.)
Anyway, the problem I'm trying to solve is pretty much the same as the problem I was trying to solve in the earlier, simpler, more linear context here.
I want to return the full path of either of these disjoint graphs, whereas anywhere within said graph the count the occurrences of SomeProperty is greater than 2.
I would think this is a common, simple problem. For example, say you had two unrelated families, and someone says, "Show me with family has more than 2 left handed people."
The super smart #cybersam recommended for the simpler incarnation of this problem, something along the lines of:
MATCH p=(a:person)-[:RELATED_TO*]->(b:person)
WHERE
NOT ()-[:RELATED_TO]->(a) AND
NOT (b)-[:RELATED_TO]->() AND
2 < REDUCE(s = 0, x IN NODES(p) | CASE WHEN x. SomeProperty = 'Yes' THEN s + 1 ELSE s END)
RETURN p;
...which works great if the graph resembles more of a straight line, and doesn't have each node in the set related to each other node.
I think the reason why #cybersam's query won't handle this more complex graph is because there is no terminal node.
(Another philosophical sidenote: I'm starting to come up with a theories that dense, intricate relationships in a graph pose combinatorial problems, with performance as well as querying. I think this might be due to the bidirectionality used by Cypher when querying?)
Here's my data. Any advice is appreciate and thanks for helping me climb the learning curve.
// match (n) detach delete n;
CREATE (albert:person {gender: 'Male', name: 'Albert', SomeProperty: 'Yes'})
CREATE (annie:person {gender: 'Female', name: 'Annie', SomeProperty: 'No'})
CREATE (adrian:person {gender: 'Female', name: 'Adrian', SomeProperty: 'No'})
CREATE (albert)-[:RELATED_TO]->(annie)
CREATE (annie)-[:RELATED_TO]->(albert)
CREATE (annie)-[:RELATED_TO]->(adrian)
CREATE (adrian)-[:RELATED_TO]->(annie)
CREATE (albert)-[:RELATED_TO]->(adrian)
CREATE (adrian)-[:RELATED_TO]->(albert)
CREATE (bill:person {gender: 'Male', name: 'Bill', SomeProperty: 'Yes'})
CREATE (barb:person {gender: 'Female', name: 'Barb', SomeProperty: 'Yes'})
CREATE (barry:person {gender: 'Male', name: 'Barry', SomeProperty: 'Yes'})
CREATE (bart:person {gender: 'Male', name: 'Bart', SomeProperty: 'No'})
CREATE (bartholemu:person {gender: 'Male', name: 'Bartholemu', SomeProperty: 'No'})
CREATE (bill)-[:RELATED_TO]->(barb)
CREATE (barb)-[:RELATED_TO]->(bill)
CREATE (barb)-[:RELATED_TO]->(barry)
CREATE (barry)-[:RELATED_TO]->(barb)
CREATE (barry)-[:RELATED_TO]->(bart)
CREATE (bart)-[:RELATED_TO]->(barry)
CREATE (bart)-[:RELATED_TO]->(bartholemu)
CREATE (bartholemu)-[:RELATED_TO]->(bart)
CREATE (bill)-[:RELATED_TO]->(bartholemu)
CREATE (bartholemu)-[:RELATED_TO]->(bill)
If this is about families of people, then easiest fix is to add a :Family node for each relational group, like so:
create (f:Family) with f
match (a:person {name:"Adrian"})-[:RELATED_TO*]->(b:person)
merge (f:Family)<-[:FAMILY]-(a)
merge (f:Family)<-[:FAMILY]-(b)
Replace "Adrian" with "Barry" to create the second family group.
That gives you a central :Family node for each family group. You can then pick the family group that has enough :person.SomeProperty = "Yes" family members like so:
// Find families with 2 or more :person.SomeProperty = "yes"
match p = (f:Family)<-[:FAMILY]-(psn:person)
where psn.SomeProperty = "Yes"
with f, count(psn) as cnt
where cnt > 2
// Get the family members
match (a:person)<-[r1:RELATED_TO]-(b:person)-[r2:RELATED_TO*]->(c)
where (a)-[:FAMILY]-(f)
and a = c // to get all the nodes in the loop
// report the first record which'll have two
// family members and all the relationships
return a, r1, b, r2
limit 1
Lets say we have a database of food items such as:
item1 = {name: 'item1', tags: ['mexican', 'spicy']};
item2 = {name: 'item2', tags: ['sweet', 'chocolate', 'nuts']};
item3 = {name: 'item3', tags: ['sweet', 'vanilla', 'cold']};
And we have a user looking for food recommendations, where they indicate their preference weight for some tags:
foodPref = {sweet: 4, chocolate: 11}
Now we need to calculate how well each item scores and recommend the best items:
item1 score = 0 (doesn't contain any of the tags user is looking for)
item2 score = 4 (contains the tag 'sweet')
item3 score = 15 (contains the tag 'sweet' and 'chocolate')
I have modeled the problem as a graph:
What's the correct way to get the recommendations -- a custom traversal object or just filter and count using AQL or just implement it in Foxx (javascript layer)?
Also, can you help out with a sample implementation for the methods you suggest?
Thanks in advance!
First, lets create the collections and their contents the way you specified them. We will add a second user.
db._create("user")
db._create("tags")
db._create("dishes")
db.user.save({_key: 'user1'})
db.user.save({_key: 'user2'})
db.tags.save({_key: 'sweet'})
db.tags.save({_key: 'chocolate'})
db.tags.save({_key: 'vanilla'})
db.tags.save({_key: 'spicy'})
db.dishes.save({_key: 'item1'})
db.dishes.save({_key: 'item2'})
db.dishes.save({_key: 'item3'})
Now lets create the edge collections with their edges:
db._createEdgeCollection("userPreferences")
db._createEdgeCollection("dishTags")
db.userPreferences.save("user/user1", "tags/sweet", {score: 4})
db.userPreferences.save("user/user1", "tags/chocolate", {score: 11})
db.userPreferences.save("user/user2", "tags/sweet", {score: 27})
db.userPreferences.save("user/user2", "tags/vanilla", {score: 7})
db.dishTags.save("tags/sweet", "dishes/item2", {score: 4});
db.dishTags.save("tags/sweet", "dishes/item3", {score: 7})
db.dishTags.save("tags/chocolate", "dishes/item2", {score: 2})
db.dishTags.save("tags/vanilla", "dishes/item3", {score: 3})
db.dishTags.save("tags/spicy", "dishes/item1", {score: 666})
Our relations are like this:
user-[userPreferences]->tags-[dishTags]->dishes
finding out what user1 likes can be done with this query:
FOR v, e IN 1..2 OUTBOUND "user/user1" userPreferences, dishTags
RETURN {item: v, connection: e}
if you now want to find all dishes that user1 likes best:
FOR v, e IN 2..2 OUTBOUND "user/user1" userPreferences, dishTags
FILTER e.score > 4 RETURN v
We filter for the score attribute.
Now we want to find another user that has the same preferences as user1 does:
FOR v, e IN 2..2 ANY "user/user1" userPreferences RETURN v
We go into ANY direction (forward and backward), but only are interested in the userPreferences edge collection, else 2..2 would also give use dishes. The way we do it now. we go back into the user collections to find users with similar preferences.
Whether or not creating a Foxx-service is a good option depends on personal preferences. Foxx is great if you want to combine & filter results on the server side, so client communication is less. You can also use it if you like to put your Application rather on top of microservices than on db-queries. Your application may then stay free of database specific code - it only operates with the microservice as its backend. There may be usecases where Foxx
In general, there is no "correct" way - there are different ways which you may prefer above others because of performance, code cleanness, scalability, etc.
I'm trying to copy a vertex node and retain it's relationships in ArangoDB. I'm getting a "access after data-modification" error (1579). It doesn't like it when I iterate over the source node's edges and insert an edge copy within the loop. This makes sense but I'm struggling to figure out how to do what I'm wanting within a single transaction.
var query = arangojs.aqlQuery`
let tmpNode = (FOR v IN vertices FILTER v._id == ${nodeId} RETURN v)[0]
let nodeCopy = UNSET(tmpNode, '_id', '_key', '_rev')
let nodeCopyId = (INSERT nodeCopy IN 'vertices' RETURN NEW._id)[0]
FOR e IN GRAPH_EDGES('g', ${nodeId}, {'includeData': true, 'maxDepth': 1})
let tmpEdge = UNSET(e, '_id', '_key', '_rev')
let edgeCopy = MERGE(tmpEdge, {'_from': nodeCopyId})
INSERT edgeCopy IN 'edges'
`;
This quesion is somewhat similar to 'In AQL how to re-parent a vertex' - so let me explain this in a similar way.
One should use the ArangoDB 2.8 pattern matching traversals to solve this.
We will copy Alice to become Sally with similar relations:
let alice=DOCUMENT("persons/alice")
let newSally=UNSET(MERGE(alice, {_key: "sally", name: "Sally"}), '_id')
let r=(for v,e in 1..1 ANY alice GRAPH "knows_graph"
LET me = UNSET(e, "_id", "_key", "_rev")
LET newEdge = (me._to == "persons/alice") ?
MERGE(me, {_to: "persons/sally"}) :
MERGE(me, {_from: "persons/sally"})
INSERT newEdge IN knows RETURN newEdge)
INSERT newSally IN persons RETURN newSally
We therefore first load Alice. We UNSET the properties ArangoDB should set on its own. We change the properties that have to be uniq to be uniq for Alice so we have a Sally afterwards.
Then we open a subquery to traverse ANY first level relations of Alice. In this subequery we want to copy the edges - e. We need to UNSET once more the document attributes that have to be autogenerated by ArangoDB. We need to find out which side of _from and _to pointed to Alice and relocate it to Sally.
The final insert of Sally has to be outside of the subquery, else this statement will attempt to insert one Sally per edge we traverse. We can't insert Saly in front of the query as you already found out - no subsequent fetches are allowed after the insert.