How can I include a list of relationships specific to each node returned in the unwind block, in a Neo4j cypher query? - neo4j

Given a graph where members of an organisation, have one or several relationships to that organisation, and members have incidents, I'm am attempting to return a list of incidents, each including the associated member and a list of their relationships to the organisation.
the graph structure:
(org)<-[MEMBER_OF|ADMIN_OF|MANAGER_OF|VISITOR_OF]-(member)-[:HAS]->(incidents)
The query I'm current attempting:
MATCH (incidents:Incident)<-[:HAS]-(member:Person)-[rels]->(org:Org {
orgId: $orgId
})
WITH org,
member,
rels,
collect(incidents) AS collectedIncidents
UNWIND collectedIncidents AS incident
RETURN incident {
. *,
org:properties(org),
member:properties(member),
roles: rels
} AS result
ORDER BY incident.createdAt DESC
SKIP $skip
LIMIT $limit
The result after I created 2 incidents on one member:
[ { member: {
createdAt: 1598524633129,
uuid: '5f430e1b-29d1-49bf-bbcc-fe1b68400378',
},
org: { createdAt: 1598524632522,
name: 'Murder Hornet Inc',
uuid: '1a69e9a1-4126-4557-b1ff-8f621b25e23d',
orgId: 'MHI',
updatedAt: 1598524632522 },
roles:
Relationship {
identity: 167,
start: 156,
end: 155,
type: 'ADMIN_OF',
properties: {}
},
uuid: '42050b82-f777-4a8a-a063-9bc43e4cf80d',
createdAt: 1598524637746,
},
{ member: {
createdAt: 1598524633129,
uuid: '5f430e1b-29d1-49bf-bbcc-fe1b68400378',
},
org: {
createdAt: 1598524632522,
name: 'Murder Hornet Inc',
uuid: '1a69e9a1-4126-4557-b1ff-8f621b25e23d',
orgId: 'MHI'
},
roles:
Relationship {
identity: 167,
start: 156,
end: 155,
type: 'ADMIN_OF',
properties: {}
},
uuid: '21c86703-4f7b-4c67-8344-0131b011b925',
createdAt: 1598524637746,
},
{ member: {
createdAt: 1598524633129,
uuid: '5f430e1b-29d1-49bf-bbcc-fe1b68400378',
},
org:
{ createdAt: 1598524632522,
name: 'Murder Hornet Inc',
uuid: '1a69e9a1-4126-4557-b1ff-8f621b25e23d',
orgId: 'MHI',
updatedAt: 1598524632522 },
roles:
Relationship {
identity: 165,
start: 156,
end: 155,
type: 'MEMBER_OF',
properties: {} },
uuid: '42050b82-f777-4a8a-a063-9bc43e4cf80d',
createdAt: 1598524637746
},
{ member:
{
createdAt: 1598524633129,
uuid: '5f430e1b-29d1-49bf-bbcc-fe1b68400378',
},
org:
{ createdAt: 1598524632522,
name: 'Murder Hornet Inc',
uuid: '1a69e9a1-4126-4557-b1ff-8f621b25e23d',
orgId: 'MHI',
},
roles:
Relationship {
identity: 165,
start: 156,
end: 155,
type: 'MEMBER_OF',
properties: {} },
uuid: '21c86703-4f7b-4c67-8344-0131b011b925',
createdAt: 1598524637746,
}
]
Note that here, I've created 2 incidents on one member, and I get 4 results back. I've also attempted to collect the rels in the WITH statement, which provides some equally odd results.
How can I get a list of unique incidents, including the related member, and the relationships that member has to the org? Ideally I would like just the relationship type in an array (given that there could be one or several relationships to the organisation).

You could use the type() and collect() functions to extract the type of relationship from rels and have them as a list.
collect(DISTINCT type(rels)) will leave you with distinct roles as an array of strings.
You might want to consider ensuring that you have DISTINCT in your collect() of incidents so that you don't have duplicate results when you unwind.
Your query when then look something like the following:
MATCH (incidents:Incident)<-[:HAS]-(member:Person)-[rels]->(org:Org {
orgId: $orgId
})
WITH org, member, collect(DISTINCT type(rels)) as roles, collect(DISTINCT incidents) AS collectedIncidents
UNWIND collectedIncidents AS incident
RETURN incident {
. *,
org:properties(org),
member:properties(member),
roles: roles
} AS result
ORDER BY incident.createdAt DESC
SKIP $skip
LIMIT $limit

Related

Typeorm get nested relations without intermediate table

I have Many-to-many relations with custom properties like here
I try to get a post with all category like
PostRepository.findOne({
relations: ['postToCategories.category'],
where: { id: 1},
})
the results like this
{
id: 1,
name: 'post1',
postToCategories: [{id: 1, category: {id: 1, name: "cate1"}}]
}
But i don't want id of intermediate table. I need something like this
{
id: 1,
name: 'post1',
postToCategories: [{category: {id: 1, name: "cate1"}}]
}
or this
{
id: 1,
name: 'post1',
postToCategories: [{id: 1, name: "cate1"}]
}
I don't need id of postToCategories. How to do that thank.

neo4j graphql #auth only read if relationship property is x

Inside #auth allow or where field, I would like to access the role of the user on PART_OF_TEAM to determine whether or not they should read/edit the posts.
As an example: a user connected to a team with the edge property "VIEWER" should only be able to read the posts inside the team, not edit them.
I am using neo4j graphql javascript plugin and I have these type definitions:
type Post #auth(
rules: [
{
operations: [READ]
allowUnauthenticated: true
allow: {
OR:[
{ visibility: PUBLIC },
{ users: { id: "$jwt.id"} }, # users reference the Post users field
{ inTeam: {users: {id: "$jwt.id"}}},
]
},
},
])
{
id: ID! #id
name: String!
"See Enum ANCHOR Visibility: PRIVATE or PUBLIC"
visibility: Visibility!
description: String
color: String!
inTeam: [Team!]! #relationship(type: "IN_TEAM", direction: OUT)
}
type User {
"generate unique id"
id: ID! #id
username: String
email: String!
password: String!
inTeams: [Team!]!
#relationship(
type: "PART_OF_TEAM"
properties: "PartOfTeam"
direction: OUT
)
}
type Team {
id: ID! #id
name: String!
users: [User!]!
#relationship(
type: "PART_OF_TEAM"
properties: "PartOfTeam"
direction: IN
)
}
interface PartOfTeam #relationshipProperties {
role: Role!
}
I already tried to search for an answer but only found this other question which was not answered:
neo4j #auth related to edge property

How can i get duplicate node CYPHER Neo4j

I have a question How can i I get some nodes with same property (for example same name property). In SQL i would use GROUP BY, but in CYPHER i don't have idea what should i use to group them. Below I added my simple input and example output to visualizate my problem.
[
{
id:1,
name: 'name1'
},
{
id:2,
name: 'name2'
},
{
id:3,
name: 'name2'
},
{
id:4,
name: 'name3'
},
{
id:5,
name: 'name3'
},
{
id:6,
name: 'name3'
},
{
id:7,
name: 'name4'
},
{
id:8,
name: 'name5'
},
{
id:9,
name: 'name6'
},
{
id:10,
name: 'name6'
}
]
My solution should gave me this:
[
{
count:2,
name: 'name2'
},
{
count:3,
name: 'name3'
},
{
count:2,
name: 'name6'
}
]
Thank you in advance for your help
In Cypher, when you aggregate (for the straight-forward cases) the grouping key is formed from the non-aggregation terms.
If nodes have already been created from the input (let's say they're using the label :Entry), then we can get the output you want with this:
MATCH (e:Entry)
RETURN e.name as name, count(e) as count
The grouping key here is name, which becomes distinct as the result of the aggregation. The result is a row for each distinct name value and the count of nodes with that name.

Is there a way to control loading relations in gorm with mongodb?

I'm building a REST api with grails 3 and mongo. I have encountered a problem when i need to marshal an object graph with a bigger depth.
I have the following domains:
class Category extends Resource {
/* other fields */
Category parent
}
class Product extends Resource {
/* other fields */
List<Category> categories
static hasMany = [categories: Category]
}
I have in the database the following structure(simplified for the sake of understanding):
categories:
{name: 'cat1'}
{name: 'cat2', parent: 'cat3'}
{name: 'cat3', parent: 'cat4'}
{name: 'cat4', parent: 'cat5'}
{name: 'cat5'}
product:
{categories: ['cat1', 'cat2']}
I am extending from RestfullController when creating my controllers. I want to be able to get a product and have the categories with parents in the returned json.
I get the following results:
/product/${id}
{
id: '...',
categories: [{
id: '...',
name: 'cat1'
}, {
id: '...',
name: 'cat2',
parent: { id: '...' }
}]
}
/category/cat2id
{
id: '...',
name: 'cat2',
parent: { id: '...' }
}
/category
[{
id: '...',
name: 'cat1'
},{
id: '...',
name: 'cat5'
},{
id: '...',
name: 'cat4',
parent: {
id: '...',
name: 'cat5'
}
},{
id: '...',
name: 'cat3',
parent: {
id: '...',
name: 'cat4',
parent: {
id: '...',
name: 'cat5'
}
}
},{
id: '...',
name: 'cat2',
parent: {
id: '...',
name: 'cat3',
parent: {
id: '...',
name: 'cat4',
parent: {
id: '...',
name: 'cat5'
}
}
}
}]
Why would Category.list() load the whole category object graph, and Category.get(), Product.get() and Product.list() would not load it? Is there a way to control this behaviour ?
The way Grails works is that it will only render associations that have already been loaded from the database, hence why you get some associations rendered and others not.
There is no built in way to control this behaviour other than writing your own marshaller. See http://grails.github.io/grails-doc/latest/guide/webServices.html#renderers

How to compose queries in Neo4j?

I want to define my Neo4j queries in terms of a generic structure (much like GraphQL) and compose them.
For example, here's a simple query with no composition:
chatrooms: {
query: {limit: 10}
fields: {
id: true,
name: true }}
My Neo4j interpreter may look like this:
chatrooms = ({query, fields}) ->
"""
MATCH (c:CHATROOMS)
RETURN #{R.join(' ', R.map(R.concat('c.'), R.keys(fields)))}
LIMIT #{query.limit}
"""
But I run into trouble when I want to compose more deeply nested queries.
For example, what if I want some information about the owner of each chatroom as well? Rather than spend two round-trip HTTP requests, I should be able to query this all at once.
chatrooms: {
query: {limit: 10}
fields: {
id: true,
name: true
owner: {
fields: {
id: true,
name: true}}}}
At this point, I get a little hung up.
I don't know how to make the interpreter generic enough to handle these cases.
I know the query should look something like this.
MATCH (c:CHATROOM)
MATCH (c)<-[:OWNS]-(u:USER)
RETURN c.id, c.name, u.id, u.name
LIMIT 10
Ideally, this query would return something with a similar structure like this:
[
{id: 1, name:'neo4j', owner: {id: 99, name: 'michael'}}
{id: 2, name:'meteor', owner: {id: 100, name: 'chet'}}
]
That would make composition and interpretation of the results much easier, but thats a detail for later.
Lastly, I'm having trouble nesting these queries even deeper.
For example, what if I want some messages for each chatroom as well?
And what if I want some information about the owner of each chatroom?
chatrooms: {
query: {limit: 10}
fields: {
id: true,
name: true
owner: {
fields: {
id: true,
name: true}}
messages: {
query: {limit: 20}
fields: {
id: true,
text: true,
createdAt: true,
owner: {
fields: {
id: true,
name: true }}}}}}
Here's my go at it -- although I'm quite sure its totally wrong.
MATCH (c:CHATROOM)
MATCH (c)<-[:OWNS]-(u:USER)
UNWIND c as room
MATCH (room)-[:OWNS]->(m:MESSAGE)
MATCH (m)<-[:OWNS]-(x:USER)
RETURN collect(m.id, m.text, m.creatdAt, x.id, x.name)
LIMIT 20
RETURN c.id, c.name, u.id, u.name,
LIMIT 10
Anyways, the goal is to be able to specify one giant nested query and be able to run it all in a single HTTP request.
Maybe some parsing of the output will be necessary, but ideally, I will get a comparable data-structure as output as well.
Any ideas?
First it's agains the GraphQL philosophy to have nested structures.
You should use references instead.
e.g.
chatrooms: {
query: {limit: 10}
fields: {
id: true,
name: true
owner: ${owner.id}
messages: {
query: {limit: 20}
fields: {
id: true,
text: true,
createdAt: true,
owner: ${owner.id}
}
}
}
owners: [{id: 1, fields: { id: true, name: true}}]
Why are you using fields property instead of fields at root object?
You can use a sequence and combination of collect({key:value}) or {key:collect(value).
MATCH (c:CHATROOM)<-[:OWNS]-(u:USER)
RETURN {id: c.id, name: c.name, owner: collect({id: u.id, name: u.name})} AS data
LIMIT 10
then with this data:
create (c:CHATROOM {id:1, name:"neo4j"})<-[:OWNS]-(u:USER {id:99,name:"Michael")
create (c:CHATROOM {id:2, name:"meteor"})<-[:OWNS]-(u:USER {id:100,name:"Chet")
running the query you get
+--------------------------------------------------------------------+
| data |
+--------------------------------------------------------------------+
| {id=2, name=meteor, owner=[{id=100, name=Chet}]} |
| {id=1, name=neo4j, owner=[{id=99, name=Michael}]} |
+--------------------------------------------------------------------+
live here: http://console.neo4j.org/r/d41luc
To combine both rows into one you would use WITH and then RETURN but then you wouldn't get the benefit of streaming your data back.
MATCH (c:CHATROOM)<-[:OWNS]-(u:USER)
WITH {id: c.id, name: c.name, owner: collect({id: u.id, name: u.name})} AS row
RETURN collect(row) as data
LIMIT 10
Update
Please use more descriptive relationship-types instead of just :OWNS everywhere.
MATCH (c:CHATROOM)<-[:OWNS]-(u:USER)
MATCH (c)-[:OWNS]->(m:MESSAGE)<-[:OWNS]-(x:USER)
WITH u,c,collect({id: m.id, text: m.text, created_at: m.createdAt, author_id: x.id, author: x.name}) as messages[0..20]
RETURN collect({ id: c.id, room: c.name,
owner_id: u.id, owner: u.name,
messages: messages}) as rooms
LIMIT 10

Resources