I have a graph like this
O
|
A
|
B
/ \
C E
/ \
D F
I want to find the path from O to F. node F can contain an attribute call "non-direct". If this attribute is set false, then the path is
O-A-B-E-F
However, if it is set to true, the path is
O-A-B-C-D-C-B-E-F
I.e., the path first has to reach B which is a terminal node and then go back to the common parent between D and F, then walk to F.
How can I create a query to return these two path based on the attribute value?
Since Neo4j will not traverse back over the same relationship, you may need to create relationships in both direction on the branches. So something like this:
In addition, I would add some properties that indicate the toplogy, so for instance add a property on-branch for the nodes C and D.
Then you can run queries like this:
MATCH (f:Thing {name:'F'}), (o:Thing {name:'O'}), (d:Thing {name:'D'})
MATCH p = (o)-[:NEXT*]->(f)
WHERE ANY(node IN nodes(p) WHERE node.`on-branch`) = f.`non-direct`
AND (d IN nodes(p)) = f.`non-direct`
RETURN REDUCE(s='', node IN nodes(p) | s+ ' '+ node.name) AS nodes
The graph I tried this on can be created from this json:
{
"nodes": [
{
"id": 0,
"labels": [
"Thing"
],
"properties": {
"name": "O",
"on-branch": false
}
},
{
"id": 1,
"labels": [
"Thing"
],
"properties": {
"name": "A",
"on-branch": false
}
},
{
"id": 2,
"labels": [
"Thing"
],
"properties": {
"name": "B",
"on-branch": false
}
},
{
"id": 3,
"labels": [
"Thing"
],
"properties": {
"name": "C",
"on-branch": true
}
},
{
"id": 4,
"labels": [
"Thing"
],
"properties": {
"name": "D",
"on-branch": true
}
},
{
"id": 5,
"labels": [
"Thing"
],
"properties": {
"name": "E",
"on-branch": false
}
},
{
"id": 6,
"labels": [
"Thing"
],
"properties": {
"name": "F",
"non-direct": false,
"on-branch": false
}
}
],
"relations": [
{
"id": 0,
"source": 0,
"target": 1,
"type": "NEXT",
"properties": {}
},
{
"id": 1,
"source": 1,
"target": 2,
"type": "NEXT",
"properties": {}
},
{
"id": 2,
"source": 3,
"target": 2,
"type": "NEXT",
"properties": {}
},
{
"id": 3,
"source": 2,
"target": 3,
"type": "NEXT",
"properties": {}
},
{
"id": 4,
"source": 4,
"target": 3,
"type": "NEXT",
"properties": {}
},
{
"id": 5,
"source": 3,
"target": 4,
"type": "NEXT",
"properties": {}
},
{
"id": 6,
"source": 2,
"target": 5,
"type": "NEXT",
"properties": {}
},
{
"id": 7,
"source": 5,
"target": 6,
"type": "NEXT",
"properties": {}
}
]
}
The json above can be loaded with this cypher (in Neo4j with apoc):
CALL apoc.load.json([ url to the json]) YIELD value AS v
UNWIND v.nodes AS node
CALL apoc.merge.node(
node.labels,
{ _tempID : node.id},
node.properties,
{}
) YIELD node AS n
WITH DISTINCT v
UNWIND v.relations AS rel
MATCH (from {_tempID: rel.source})
MATCH (to {_tempID: rel.target})
CALL apoc.merge.relationship(
from,
rel.type,
{},
rel.properties,
to,
{}
) YIELD rel AS r
WITH DISTINCT v
MATCH (n) WHERE EXISTS(n._tempID)
REMOVE n._tempID
Related
what I need is to refine this query:
MATCH
(u:User)<-[:CAN_SEE]-(c:Company)-[:CAN_SEE*]->(tc:UserView)
WHERE NOT (u:User)<-[:CAN_SEE]-(c:Company)-[:CAN_SEE*]->(:User)-[:CAN_SEE*]->(tc:UserView)
AND u.entityId=$userId and tc.entityId=$targetUserViewId
RETURN tc
I need to find UserView nodes directly visible from User u without passing through another User.
EDIT: I'M NOT searching "what users are directly pointing to a given view". I want to know what views can be reached from a user passing through his own attached company and potentially other nodes (not presented in the example graph). See sample data and the last phrase of the question for a reference of the intended result.
The query works fine if there is a single path to the UserView: if the path is direct, tc is returned; else, if the path goes through another user, no result is returned.
The problem arises if there are more paths from u to tc, some legal, and some not: I want that tc is returned, because at least a good path exists, but I get no result.
I understand that my query is wrong, because it says "search a tc that can't be accessed passing from another user", but what I really need is "search a tc that can be accessed in some manner without passing from another user"...
How can I fix my query?
Sample data:
[
{
"n": {
"identity": 0,
"labels": [
"Tenant",
"VisibilityGroup"
],
"properties": {
"name": "Tenant 2b2fa6af-b18d-4839-bb61-1657a8573b37",
"entityId": "2b2fa6af-b18d-4839-bb61-1657a8573b37",
"id": "a5910cbe-0084-4e71-a722-724653b96a4e",
"entityRef": "com.tierra.userservice.model.entity.Tenant:2b2fa6af-b18d-4839-bb61-1657a8573b37",
"isViewItem": false
}
}
},
{
"n": {
"identity": 1,
"labels": [
"Company",
"VisibilityGroup"
],
"properties": {
"name": "Company d88cac2a-3c11-4088-8f9c-a6a44edf7023",
"entityId": "d88cac2a-3c11-4088-8f9c-a6a44edf7023",
"id": "2d5e3c31-ff86-410d-8aa5-6d638a38141b",
"entityRef": "com.tierra.userservice.model.entity.company.Company:d88cac2a-3c11-4088-8f9c-a6a44edf7023",
"isViewItem": false
}
}
},
{
"n": {
"identity": 2,
"labels": [
"User",
"VisibilityGroup"
],
"properties": {
"name": "User 76f1bf95-6819-4aea-b1c3-4d138eb40cd3",
"entityId": "76f1bf95-6819-4aea-b1c3-4d138eb40cd3",
"id": "00b77fc9-7c90-4e89-ae24-3fe2468afea5",
"entityRef": "com.tierra.userservice.model.entity.user.User:76f1bf95-6819-4aea-b1c3-4d138eb40cd3",
"isViewItem": false
}
}
},
{
"n": {
"identity": 3,
"labels": [
"Tenant",
"VisibilityGroup"
],
"properties": {
"name": "Tenant 7b8f4284-3e55-4e40-a8e8-6aee0b22dc97",
"entityId": "7b8f4284-3e55-4e40-a8e8-6aee0b22dc97",
"id": "55ad9591-0ec1-44b4-ba20-ff1218e0edce",
"entityRef": "com.tierra.userservice.model.entity.Tenant:7b8f4284-3e55-4e40-a8e8-6aee0b22dc97",
"isViewItem": false
}
}
},
{
"n": {
"identity": 4,
"labels": [
"Company",
"VisibilityGroup"
],
"properties": {
"name": "Company 9eddb71f-1126-443f-8e87-bf0ff14ef582",
"entityId": "9eddb71f-1126-443f-8e87-bf0ff14ef582",
"id": "ccc3e2e7-2001-401d-b199-1aa2e3b99d28",
"entityRef": "com.tierra.userservice.model.entity.company.Company:9eddb71f-1126-443f-8e87-bf0ff14ef582",
"isViewItem": false
}
}
},
{
"n": {
"identity": 5,
"labels": [
"User",
"VisibilityGroup"
],
"properties": {
"name": "User ffe8ac6a-b1fd-43a2-bb5c-112aba2e5288",
"entityId": "ffe8ac6a-b1fd-43a2-bb5c-112aba2e5288",
"id": "eec0daf3-52c5-4ebc-8e4d-2a93316ea40b",
"entityRef": "com.tierra.userservice.model.entity.user.User:ffe8ac6a-b1fd-43a2-bb5c-112aba2e5288",
"isViewItem": false
}
}
},
{
"n": {
"identity": 6,
"labels": [
"Company",
"VisibilityGroup"
],
"properties": {
"name": "Company 0738f2f7-2810-4358-a291-94a5e919469d",
"entityId": "0738f2f7-2810-4358-a291-94a5e919469d",
"id": "5657308e-a20e-4199-b92b-5659f36c39a9",
"entityRef": "com.tierra.userservice.model.entity.company.Company:0738f2f7-2810-4358-a291-94a5e919469d",
"isViewItem": false
}
}
},
{
"n": {
"identity": 7,
"labels": [
"User",
"VisibilityGroup"
],
"properties": {
"name": "User 5c4fef7f-1a41-4b09-a0f5-2034510dcf92",
"entityId": "5c4fef7f-1a41-4b09-a0f5-2034510dcf92",
"id": "d0b9798c-b7de-4761-a5a7-eaed4ca8a058",
"entityRef": "com.tierra.userservice.model.entity.user.User:5c4fef7f-1a41-4b09-a0f5-2034510dcf92",
"isViewItem": false
}
}
},
{
"n": {
"identity": 8,
"labels": [
"UserView",
"VisibilityGroup"
],
"properties": {
"name": "UserView 72bed633-c422-40f9-a7e1-cb41af27d70a",
"entityId": "72bed633-c422-40f9-a7e1-cb41af27d70a",
"id": "c5690b5a-5376-47fe-82dc-ea91799175f6",
"entityRef": "com.tierra.userservice.model.entity.user.UserView:72bed633-c422-40f9-a7e1-cb41af27d70a",
"isViewItem": false
}
}
},
{
"n": {
"identity": 9,
"labels": [
"UserView",
"VisibilityGroup"
],
"properties": {
"name": "UserView fbd5cb12-0685-46e6-a672-2770f2140bf5",
"entityId": "fbd5cb12-0685-46e6-a672-2770f2140bf5",
"id": "185f1b3f-8737-4ef0-b0af-bdee05352d5a",
"entityRef": "com.tierra.userservice.model.entity.user.UserView:fbd5cb12-0685-46e6-a672-2770f2140bf5",
"isViewItem": false
}
}
}
]
Now execute:
MATCH
(u:User)<-[:CAN_SEE]-(c:Company)-[:CAN_SEE*]->(tc:UserView)
WHERE NOT (u:User)<-[:CAN_SEE]-(c:Company)-[:CAN_SEE*]->(:User)-[:CAN_SEE*]->(tc:UserView)
AND u.entityId='76f1bf95-6819-4aea-b1c3-4d138eb40cd3' and tc.entityId='fbd5cb12-0685-46e6-a672-2770f2140bf5'
RETURN tc
This returns nothing, I would like to have the view fbd5cb12-0685-46e6-a672-2770f2140bf5, id=9 because a path exists between the user and the view NOT passing through another User.
This seems to do the trick, I hope it's correct... but I think there should be a much better solution using another description of the path...
MATCH p=
(u:User)<-[:CAN_SEE]-(c:Company)-[:CAN_SEE*]->(tc:UserView)
WHERE u.entityId='76f1bf95-6819-4aea-b1c3-4d138eb40cd3' and tc.entityId='fbd5cb12-0685-46e6-a672-2770f2140bf5'
AND none(x IN NODES(p) WHERE ( 'User' IN LABELS(x) and x<>u ))
RETURN tc
... find UserView nodes directly visible from User u without passing
through another User
MATCH (tc:UserView)<-[:CAN_SEE]-(u:User)
WHERE u.entityId='76f1bf95-6819-4aea-b1c3-4d138eb40cd3'
RETURN tc
Or, if you want to limit it to a specific UserView:
MATCH (tc:UserView)<-[:CAN_SEE]-(u:User)
WHERE
u.entityId='76f1bf95-6819-4aea-b1c3-4d138eb40cd3' AND
tc.entityId='fbd5cb12-0685-46e6-a672-2770f2140bf5'
RETURN tc
First of all, I'm new to NEO4J and to CYPHER. So I'm twerking here and there to figure out to get the result I want.
Below is my graph. Let's say it's a simple family tree.
I have come up with this simple cypher query to fetch the direct descendants of the node
MATCH (p:Person {username: "SETHLORDM"})<-[r:CHILD_OF]-(p2)
RETURN {current: p, children: collect(p2)}
and the text version of the result is as below
The above is okay, but I want to get the text result as follows if it's doable with NEO4J.
[
{
"username": "SETHLORDM",
"location": "NO_LOCATION",
"children": [
{
"username": "TESTNODE_1",
"location": "LEFT",
"children": [
{
"username": "TESTNODE_3",
"location": "LEFT",
"children": []
},
{
"username": "TESTNODE_4",
"location": "RIGHT",
"children": []
}
],
},
{
"username": "TESTNODE_2",
"location": "RIGHT",
"children": [
{
"username": "TESTNODE_5",
"location": "RIGHT",
"children": []],
},
{
"username": "TESTNODE_6",
"location": "RIGHT",
"children": []],
}
],
}
],
}
]
Any help regarding this would be highly appreciated. Thank you
One way to approach it is using apoc.convert.toTree (using the plugin apoc).
This can create the tree structure that you are looking for. But, since your tree is bottom-up, the result will be same, meaning each node will point its parent. If you want to get the results as you want, using this method, you will have to change your relations.
For example, using this data:
MERGE (a:Person{key: 1, username: "SETHLORDM"})
MERGE (b:Person{key: 2})
MERGE (c:Person{key: 3})
MERGE (d:Person{key: 4})
MERGE (e:Person{key: 5})
MERGE (f:Person{key: 6})
MERGE (g:Person{key: 7})
MERGE (b)-[:CHILD_OF]-(a)
MERGE (c)-[:CHILD_OF]-(a)
MERGE (d)-[:CHILD_OF]-(b)
MERGE (e)-[:CHILD_OF]-(b)
MERGE (f)-[:CHILD_OF]-(c)
MERGE (g)-[:CHILD_OF]-(c)
and this query:
MATCH path = (p:Person {username: "SETHLORDM"})<-[r:CHILD_OF*..2]-(p2)
WITH collect(path) AS paths
CALL apoc.convert.toTree(paths)
YIELD value
RETURN value;
will give this result:
"_type": "Person",
"child_of": [
{
"_type": "Person",
"child_of": [
{
"_type": "Person",
"_id": 243,
"key": 5
},
{
"_type": "Person",
"_id": 242,
"key": 4
}
],
"_id": 240,
"key": 2
},
{
"_type": "Person",
"child_of": [
{
"_type": "Person",
"_id": 245,
"key": 7
},
{
"_type": "Person",
"_id": 244,
"key": 6
}
],
"_id": 241,
"key": 3
}
],
"_id": 239,
"key": 1,
"username": "SETHLORDM"
}
But changing the links to this:
MERGE (a)-[:CHILDREN]-(b)
MERGE (a)-[:CHILDREN]-(c)
MERGE (b)-[:CHILDREN]-(d)
MERGE (b)-[:CHILDREN]-(e)
MERGE (c)-[:CHILDREN]-(f)
MERGE (c)-[:CHILDREN]-(g)
And adjusting the query to:
MATCH path = (p:Person {username: "SETHLORDM"})-[r:CHILDREN*..2]->(p2)
WITH collect(path) AS paths
CALL apoc.convert.toTree(paths)
YIELD value
RETURN value;
Will provide:
{
"_type": "Person",
"_id": 246,
"children": [
{
"_type": "Person",
"_id": 247,
"children": [
{
"_type": "Person",
"_id": 249,
"key": 4
},
{
"_type": "Person",
"_id": 250,
"key": 5
}
],
"key": 2
},
{
"_type": "Person",
"_id": 248,
"children": [
{
"_type": "Person",
"_id": 252,
"key": 7
},
{
"_type": "Person",
"_id": 251,
"key": 6
}
],
"key": 3
}
],
"key": 1,
"username": "SETHLORDM"
}
Which is now similar to what you wanted...
Bonus: if you are using apoc, you can replace the MATCH query by apoc.path.expandConfig which should be more efficient to larger graphs.
I have this grammar:
let lexer = moo.compile({
comment: { match: /[\/\/.*?$|][^\n]+/, value: (s:string) => s.slice(1).trim() },
newline: { match: /[\n]+/, lineBreaks: true }
});
%}
#lexer lexer
main ->
element
| main %newline element
element -> comment
comment -> %comment
Now when I feed nearley the following input: //\n//\n//\n//\n// I get this result:
[
[
[
[
[
[
[
[
{
"type": "comment",
"value": "/",
"text": "//",
"offset": 0,
"lineBreaks": 0,
"line": 1,
"col": 1
}
]
]
],
{
"type": "newline",
"value": "\n",
"text": "\n",
"offset": 2,
"lineBreaks": 1,
"line": 1,
"col": 3
},
[
[
{
"type": "comment",
"value": "/",
"text": "//",
"offset": 3,
"lineBreaks": 0,
"line": 2,
"col": 1
}
]
]
],
{
"type": "newline",
"value": "\n",
"text": "\n",
"offset": 5,
"lineBreaks": 1,
"line": 2,
"col": 3
},
[
[
{
"type": "comment",
"value": "/",
"text": "//",
"offset": 6,
"lineBreaks": 0,
"line": 3,
"col": 1
}
]
]
],
{
"type": "newline",
"value": "\n",
"text": "\n",
"offset": 8,
"lineBreaks": 1,
"line": 3,
"col": 3
},
[
[
{
"type": "comment",
"value": "/",
"text": "//",
"offset": 9,
"lineBreaks": 0,
"line": 4,
"col": 1
}
]
]
],
{
"type": "newline",
"value": "\n",
"text": "\n",
"offset": 11,
"lineBreaks": 1,
"line": 4,
"col": 3
},
[
[
{
"type": "comment",
"value": "/",
"text": "//",
"offset": 12,
"lineBreaks": 0,
"line": 5,
"col": 1
}
]
]
]
]
I dont quite understand why the resulting array is so deeply nested and if theres a way to just have it flat for each elements. Like comments on the same semantic level should be part of one array and not nested.
Okay, so it turns out you have to pass a post-processor to each rule if you don't want them nested in arrays.
For instance like this:
main ->
element {% d => ({ type: "main_element", data: d[0]}) %}
| main %newline element {% d => ({ type: "main_element", data: d[2], main_data: d[0]}) %}
element -> %comment
{% d => ({ type: "element", data: d[0]}) %}
This will result in a flat structure as expected:
[
{
"type": "main_element",
"data": {
"type": "element",
"data": {
"type": "comment",
"value": "/",
"text": "//",
"offset": 12,
"lineBreaks": 0,
"line": 5,
"col": 1
}
},
"main_data": {
"type": "main_element",
"data": {
"type": "element",
"data": {
"type": "comment",
"value": "/",
"text": "//",
"offset": 9,
"lineBreaks": 0,
"line": 4,
"col": 1
}
},
"main_data": {
"type": "main_element",
"data": {
"type": "element",
"data": {
"type": "comment",
"value": "/",
"text": "//",
"offset": 6,
"lineBreaks": 0,
"line": 3,
"col": 1
}
},
"main_data": {
"type": "main_element",
"data": {
"type": "element",
"data": {
"type": "comment",
"value": "/",
"text": "//",
"offset": 3,
"lineBreaks": 0,
"line": 2,
"col": 1
}
},
"main_data": {
"type": "main_element",
"data": {
"type": "element",
"data": {
"type": "comment",
"value": "/",
"text": "//",
"offset": 0,
"lineBreaks": 0,
"line": 1,
"col": 1
}
}
}
}
}
}
}
]
I have nodes with these properties:
MATCH (n:A) RETURN n
[
{
"name": "114s09A.1",
"_id": "114s09A.1",
"id": "114s09A.1",
"created_n4j": "2020-12-21T09:56:11.256000000Z",
"type": "A",
"updated_n4j": "2020-12-21T09:56:11.256000000Z"
},
{
"name": "114s09A.2",
"_id": "114s09A.2",
"id": "114s09A.2",
"created_n4j": "2020-12-21T09:56:11.257000000Z",
"type": "A",
"updated_n4j": "2020-12-21T09:56:11.257000000Z"
}
]
Is there a way to build the cypher query so that the result would be shaped as a dictionary where id would be the key ?
[
{
"114s09A.1": {
"name": "114s09A.1",
"id": "114s09A.1",
"created_n4j": "2020-12-21T09:56:11.256000000Z",
"type": "A",
"updated_n4j": "2020-12-21T09:56:11.256000000Z"
}
},
{
"114s09A.2": {
"name": "114s09A.2",
"id": "114s09A.2",
"created_n4j": "2020-12-21T09:56:11.257000000Z",
"type": "A",
"updated_n4j": "2020-12-21T09:56:11.257000000Z"
}
}
]
The closest I came up to so far is :
MATCH (n:A) RETURN n._id AS _id, properties(n) AS properties
[
{
"_id":"114s09A.1",
"properties":{
"name": "114s09A.1",
"id": "114s09A.1",
"created_n4j": "2020-12-21T09:56:11.256000000Z",
"type": "A",
"updated_n4j": "2020-12-21T09:56:11.256000000Z"
}
},
{
"_id":"114s09A.2",
"properties":{
"name": "114s09A.2",
"id": "114s09A.2",
"created_n4j": "2020-12-21T09:56:11.257000000Z",
"type": "A",
"updated_n4j": "2020-12-21T09:56:11.257000000Z"
}
}
]
It's not possible with the default Cypher syntax, however if you have the apoc library installed, you can do this :
MATCH (n:A)
RETURN apoc.map.setKey({}, n.id, n{.*})
I have a query that I ran in the neo4j web browser:
MATCH p=(z)<-[*]-(a)-[:foo]->(b) WHERE b.value = "bar" return p
This returned a large number of connected nodes. Something happened that seems to have deleted all of these nodes (in a separate incident), but I still have the output of the old query. The code section of the browser has the response data listed:
...
"graph": {
"nodes": [
{
"id": "1148578",
"labels": [
"text"
],
"properties": {
"value": "bar",
"timestamp": 1478747946867
}
},
...
Is there a way for me to recreate all of this data from the output of an old query?
You could use apoc.load.json to do this. Note that this solution will not preserve the internal node ids. APOC is a procedure library that extends built-in Neo4j functionality.
Given the JSON file
{"graph": {
"nodes": [
{
"id": "32496",
"labels": [
"Person"
],
"properties": {
"born": 1967,
"name": "Carrie-Anne Moss"
}
},
{
"id": "32505",
"labels": [
"Movie"
],
"properties": {
"tagline": "Evil has its winning ways",
"title": "The Devil's Advocate",
"released": 1997
}
},
{
"id": "32494",
"labels": [
"Movie"
],
"properties": {
"tagline": "Welcome to the Real World",
"title": "The Matrix",
"released": 1999
}
},
{
"id": "32495",
"labels": [
"Person"
],
"properties": {
"born": 1964,
"name": "Keanu Reeves"
}
}
],
"relationships": [
{
"id": "83204",
"type": "ACTED_IN",
"startNode": "32495",
"endNode": "32505",
"properties": {
"role": "Kevin Lomax"
}
},
{
"id": "83183",
"type": "ACTED_IN",
"startNode": "32496",
"endNode": "32494",
"properties": {
"role": "Trinity"
}
},
{
"id": "83182",
"type": "ACTED_IN",
"startNode": "32495",
"endNode": "32494",
"properties": {
"role": "Neo"
}
}
]
}
}
}
We can recreate the graph using this query:
CALL apoc.load.json("https://dl.dropboxusercontent.com/u/67572426/small_movie_graph.json") YIELD value AS row
WITH row, row.graph.nodes AS nodes
UNWIND nodes AS node
CALL apoc.create.node(node.labels, node.properties) YIELD node AS n
SET n.id = node.id
WITH row
UNWIND row.graph.relationships AS rel
MATCH (a) WHERE a.id = rel.startNode
MATCH (b) WHERE b.id = rel.endNode
CALL apoc.create.relationship(a, rel.type, rel.properties, b) YIELD rel AS r
RETURN *