How to compose queries in Neo4j? - 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

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.

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.

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

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

Tagging media with grouped tags using Neo4j and Cypher

Cypher newbie here.
I made this graph of tagged Media using the code below.
CREATE
(funny:Tag { name: 'Funny' }),
(sad:Tag { name: 'Sad' }),
(movie:Tag { name: 'Movie' }),
(tv:Tag { name: 'TV Show' }),
(hangover:Media { name: 'The Hangover' }),
(koth:Media { name: 'King of the Hill' }),
(simpsons:Media { name: 'The Simpsons' }),
(twm:Media { name: 'Tuesdays with Morrie' }),
(mm:Media { name: 'Mary & Max' }),
(funny)-[:DESCRIBES]->(hangover),
(funny)-[:DESCRIBES]->(koth),
(funny)-[:DESCRIBES]->(simpsons),
(sad)-[:DESCRIBES]->(twm),
(sad)-[:DESCRIBES]->(mm),
(movie)-[:DESCRIBES]->(hangover),
(movie)-[:DESCRIBES]->(twm),
(movie)-[:DESCRIBES]->(mm),
(tv)-[:DESCRIBES]->(koth),
(tv)-[:DESCRIBES]->(simpsons)
What I want to do is group Tags together into Contexts, such that one Context node has the same meaning as multiple Tags.
MATCH
(tf:Tag { name: 'Funny' }),
(tr:Tag { name: 'Sad' }),
(tm:Tag { name: 'Movie' })
(tt:Tag { name: 'TV Show' })
CREATE
(fm:Context { name: 'Funny Movies' }),
(ft:Context { name: 'Funny TV' }),
(s:Context { name: 'Sad Movies' }),
(fm)-[:INCLUDES]->(tf),
(fm)-[:INCLUDES]->(tm),
(ft)-[:INCLUDES]->(tf),
(ft)-[:INCLUDES]->(tt),
(s)-[:INCLUDES]->(tm),
(s)-[:INCLUDES]->(tr)
So now we have this thing.
I want to take a Context node and get Media such that ALL Tags in that Context describe each returned Media.
I tried MATCH (c:Context { name: 'Funny Movies' })-[:INCLUDES]->()-[:DESCRIBES]->(m) RETURN m to match media tagged with both Funny and Movies. The expected output was only The Hangover, but I get all Media instead.
It's pretty obvious that I don't understand the kind of query I need to write. What is wrong with my query, and how can I produce the output that I want?
When you start from a context, you can collect the tags and then match movies that are related to ALL the tags. The highlighted words in the previous sentence are the keywords for you as reference in the neo4j documentation :
MATCH (c:Context {name:"Funny Movies"})-[:INCLUDES]->(tag)
WITH collect(tag) AS tags
MATCH (m:Media) WHERE ALL( x IN tags WHERE (x)-[:DESCRIBES]->(m))
RETURN m
You can use the bi-directional pattern:
MATCH (c:Context { name: 'Funny Movies' })-[:INCLUDES]->()-[:DESCRIBES]
->(m)<-
[:DESCRIBES]-()<-[:INCLUDES]-(c)
RETURN m

Additional attributes in N-N mongoid (Like in pivot table)

I have a has_many_and_belongs_to relationship between central and coordinators.
So my mongo documents are represented as below:
central = {
_id: 1,
title: 'First Name',
coordinators: [
BSON[1],
BSON[2],
BSON[3]
]
}
coordinators = [
{
_id: 1,
name: 'Me',
centrals: [BSON[1], BSON[2]]
},
{
_id: 1,
name: 'Mateus'
centrals: [BSON[1]]
},
{
_id: 1,
name: 'Gomes'
centrals: [BSON[1]]
},
]
If I do this:
#central = Central.find(1)
#coordinator = #central.coordinators.find(1)
#coordinator.can_edit = false
It will apply to the coordinators document resulting in this:
coordinator = {
_id: 1,
name: 'Me',
centrals: [BSON[1], BSON[2]],
can_edit: false
}
But what I really want to do is apply this can_edit attribute in the relationship, like in pivot table in RDBMS:
central = {
_id: 1,
titulo: 'First Name',
coordinators: [
{
_id: 1,
name: 'Me',
can_edit: false
},
BSON[2],
BSON[3]
]
}
Only for the central with id 1 I want to aply the can_edit to false.
I have to keep the relation between a Central and Coordinator, but in some situation, I want to have an additional information about that relation, like if I would not allow a coordinator to edit some data only in central with id 1.
How can I do this using mongoid?
The solution for this was create another relation N-N:
Add on central.rb
has_and_belongs_to_many :blocked_coordenadors,
class_name: "Central",
inverse_of: :blocked_centrals
And in coordinator.rb:
has_and_belongs_to_many :blocked_centrals,
class_name: "Central",
inverse_of: :blocked_coordenadors
And to check I do this:
central.blocked_coordenadors.include? coordinator

Resources