Tagging media with grouped tags using Neo4j and Cypher - neo4j

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

Related

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.

Cypher query to return graph as object, where each parent node contains an array of its children?

I have a graph like the one below:
What cypher query must I use to return the graph as an object, where each parent node contains an array of its children nodes?
Example:
state: {
name: 'New York'
publishingCompanies: [
{
name: 'Penguin'
authors: [
{
name: 'George Orwell',
books: [
{ name: 'Why I Write'},
{ name: 'Animal Farm'},
{ name: '1984' }
]
},
{
name: 'Vladimir Nobokov'
books: [
{ name: 'Lolita' }
]
},
...
]
},
{
name: 'Random House',
authors: [
...
]
}
]
}
I tried to use apoc.convert.toTree, but it returned an array of paths from State to Book.
This should return the state object (assuming the state name is passed in via the stateName parameter):
MATCH (s:State)
WHERE s.name = $stateName
OPTIONAL MATCH (s)-[:IS_STATE_OF]->(c)
OPTIONAL MATCH (c)-[:PUBLISHED_FOR]->(a)
OPTIONAL MATCH (a)-[:WROTE]->(b)
WITH s, c, a, CASE WHEN b IS NULL THEN [] ELSE COLLECT({name: b.name}) END AS books
WITH s, c, CASE WHEN a IS NULL THEN [] ELSE COLLECT({name: a.name, books: books}) END AS authors
WITH s, CASE WHEN c IS NULL THEN [] ELSE COLLECT({name: c.name, authors: authors}) END AS pcs
RETURN {name: s.name, publishingCompanies: pcs} AS state

Auto-generated Mutation Does Not Create Relationship

I want to test auto-generated CRUD mutations created by calling makeAugmentedSchema from 'neo4j-graphql-js'. There is no problem with creating nodes but creating relationship does not work for me. Please advise on what I am doing wrong here.
Schema:
type Bio{
id: ID!
description: String
}
type Person{
id: ID!
name: String
dob: Date
gender: String
bioRelation: [Bio] #relation(name: "HAS_BIO", direction: "OUT")
}
Mutation:
I am following the Interface Mutations guidance https://grandstack.io/docs/graphql-interface-union-types to create mutation.
mutation {
p: CreatePerson(
name: "Anton",
gender: "Male") {
name
gender
id
}
b: CreateBio(
description: "I am a developer") {
description
id
}
r: AddPersonBioRelation(
from: {id: "p"},
to:{id: "b"}
){
from{
name
}
to{
description
}
}
}
It create Person and Bio nodes but no any relationship gets created between the two:
{
"data": {
"p": {
"name": "Anton",
"gender": "Male",
"id": "586b63fd-f9a5-4274-890f-26ba567c065c"
},
"b": {
"description": "I am a developer",
"id": "a46b4c22-d23b-4630-ac84-9d6248bdda89"
},
"r": null
}
}
This is how AddPersonBioRelation looks like:
Thank you.
I am new to GRANDstack, and I have also been struggling with these types of issues myself. I have typically broken this out separate mutations (in javascript) and used the return value for each as values for the next mutation. for example:
await createIncident({
variables: {
brief: values.brief,
date,
description: values.description,
recordable: values.recordable,
title: values.title
}
}).then((res) => {
addIncidentUser({
variables: {
from: user.id,
to: res.data.CreateIncident.id
}
});
});
the problem that i see in the example you've provided is that you are specifying a string value for from and to as "p" and "b" respectively and NOT the p.id and b.id return values from the parent mutations.
it's fine of me to point that out but what i can't for the LIFE of me figure out is how to properly reference p.id and b.id in the mutation itself. in other words you are trying to send
from: { id: "586b63fd-f9a5-4274-890f-26ba567c065c"}
to: { id: "a46b4c22-d23b-4630-ac84-9d6248bdda89" }
but in reality you are sending
from: { id: "p"}
to: { id: "b" }
which aren't actually references in neo4j so it fails.
if we can figure out how to properly reference p.id and b.id we should get this working.
Thank you, #dryhurst. It appears that there is no way to reference id of newly created nodes, but I found a solution by introducing temp id property. Please see the discussion of this matter and final solution on:
https://community.neo4j.com/t/auto-generated-mutation-does-not-create-relationship/21412/16.

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

Correct usage of CREATE UNIQUE and/or MERGE, example

The following query:
MATCH (LOCALID0:Ship {ShipID: "12345", ShipName: "Phil's Yacht"} )
CREATE UNIQUE (LOCALID0:Ship {ShipID: "12345", ShipName: "Phil's Yacht" } ) - [LOCALID1:contains] - > (LOCALID2:container {ContainerID: "91812" } ),
(LOCALID0) - [LOCALID3:contains] - > (LOCALID4:container {ContainerID: "87132" } ),
(LOCALID0) - [LOCALID5:contains] - > (LOCALID6:container {ContainerID: "47490" } ),
(LOCALID0) - [LOCALID7:contains] - > (LOCALID8:container {ContainerID: "13157" } ),
(LOCALID0) - [LOCALID9:contains] - > (LOCALID10:container {ContainerID: "22676" } )
Gives the following error:
Exception in thread "main" Can't create `LOCALID0` with properties or labels here. It already exists in this context
When I reuse one of the Cypher ID's, so I have to keep reusing its properties? Is the problem that it doesnt realise that LOCALID0:Ship {ShipID: "12345", ShipName: "Phil's Yacht"} is the same node as LOCALID0?
No, you just re-use the identifier:
MATCH (LOCALID0:Ship { ShipID: "12345", ShipName: "Phil's Yacht" })
CREATE UNIQUE
(LOCALID0)-[LOCALID1:contains]- >(LOCALID2:container { ContainerID: "91812" }),
(LOCALID0)-[LOCALID3:contains]- >(LOCALID4:container { ContainerID: "87132" }),
(LOCALID0)-[LOCALID5:contains]- >(LOCALID6:container { ContainerID: "47490" }),
(LOCALID0)-[LOCALID7:contains]- >(LOCALID8:container { ContainerID: "13157" }),
(LOCALID0)-[LOCALID9:contains]- >(LOCALID10:container { ContainerID: "22676" })
This will create the container pattern provided that LOCALID0 is matched, else it will not.
If you were looking to create the ship + container pattern if the ship is not found(and
create this pattern once only), then you might try:
MERGE (LOCALID0:Ship { ShipID: "12345", ShipName: "Phil's Yacht" })
CREATE UNIQUE
(LOCALID0)-[LOCALID1:contains]- >(LOCALID2:container { ContainerID: "91812" }),
(LOCALID0)-[LOCALID3:contains]- >(LOCALID4:container { ContainerID: "87132" }),
(LOCALID0)-[LOCALID5:contains]- >(LOCALID6:container { ContainerID: "47490" }),
(LOCALID0)-[LOCALID7:contains]- >(LOCALID8:container { ContainerID: "13157" }),
(LOCALID0)-[LOCALID9:contains]- >(LOCALID10:container { ContainerID: "22676" })

Resources