Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.
11-26-2018 09:32 AM
I am trying to transform a group of nodes into a map. I am not sure if this is best done with straight Cypher or whether there is an apoc function to help. ... maybe a combination of both?
If I have the following nodes
CREATE (e1:EntityDef{name:"Person"})
MERGE (e1)-[:HAS_A]->(:Property{name:"givenName",label:"Given Name",length:35})
MERGE (e1)-[:HAS_A]->(:Property{name:"surname",label:"Surname",length:35})
MERGE (e1)-[:HAS_A]->(:Property{name:"dob",label:"Date of Birth",format:"dd-mm-yyyy"})
CREATE (e2:EntityDef{name:"Organisation"})
MERGE (e2)-[:HAS_A]->(:Property{name:"name",label:"Name",length:40})
MERGE (e2)-[:HAS_A]->(:Property{name:"established",label:"Established",format:"dd-mm-yyyy"})
I would like to get this map.
=> {Person:[{name:"givenName",label:"Given Name",length:35},
{name:"surname",label:"Surname",length:35},
{name:"dob",label:"Date of Birth",format:"dd-mm-yyyy"}],
Organisation:[{name:"name",label:"Name",length:40},
{name:"established",label:"Established",format:"dd-mm-yyyy"}]
}
Can some one suggest an aproach to this?
I have considered 'apoc.map.fromNodes', but it seems that I need to provide a single property.
Solved! Go to Solution.
11-28-2018 02:16 AM
In that case you'll want to keep apoc.map.fromValues()
at the very end:
RETURN collect(apoc.map.fromValues([d.name, properties])) as entityDefs
11-26-2018 06:51 PM
I tried my hand at this for several minutes, but to no avail: couldn't get values stored in variables to be used as map keys... So I googled a bit and came to this blog: https://markhneedham.com/blog/2017/09/19/neo4j-cypher-create-cypher-map-with-dynamic-keys/
From there, I was able to formulate this query, which comes pretty dang close to what you want:
MATCH (p:EntityDef)-[:HAS_A]->(n)
WITH p.name AS dynamicKey, collect(n) AS dynamicValue
RETURN apoc.map.fromValues([dynamicKey, dynamicValue]) AS map
The last step is to combine the two maps into one... But I ran out of time at the moment. Good luck! Let me know if you figure out that last step.
11-26-2018 07:24 PM
Had a chance to come back and figure it out, and -- glad to say -- figure it out I did!
MATCH (p:EntityDef)-[:HAS_A]->(n)
WITH p.name AS dynamicKey, collect(n) AS dynamicValue
WITH apoc.map.fromValues([dynamicKey, dynamicValue]) as map
WITH collect(map) as map_list
WITH map_list[0] as first,
map_list[1] as second
RETURN apoc.map.merge(first, second)
Output:
{
"Organisation": [
{
"format": "dd-mm-yyyy",
"name": "established",
"label": "Established"
},
{
"name": "name",
"length": 40,
"label": "Name"
}
],
"Person": [
{
"format": "dd-mm-yyyy",
"name": "dob",
"label": "Date of Birth"
},
{
"length": 35,
"name": "surname",
"label": "Surname"
},
{
"length": 35,
"name": "givenName",
"label": "Given Name"
}
]
}
11-26-2018 11:42 PM
Thanks for your time.
11-28-2018 12:39 AM
The approach I ended up taking is
MATCH (d:EntityDef)
OPTIONAL MATCH (d)-[:HAS_A]->(p:Property)-[:OF]->(t:PropertyType)
WITH d,collect(apoc.map.merge(properties(p),{type:t.name})) as properties
RETURN collect({name:d.name,properties:properties}) as entityDefs
This query includes a PropertyType that was not in my initial example, without it we would not have needed the merge.
11-28-2018 12:58 AM
Looks pretty good, here's a few recommendations that use map projection to make assembling those maps a bit easier:
MATCH (d:EntityDef)
OPTIONAL MATCH (d)-[:HAS_A]->(p:Property)-[:OF]->(t:PropertyType)
WITH d,collect(p {.*, type:t.name}) as properties
RETURN collect(d {.name, properties}) as entityDefs
11-28-2018 01:03 AM
"Map Projection" that is what I was looking for! I could not recall the syntax (or the correct term) and so missed it in the cypher ref card.
Thanks a heap.
11-28-2018 01:59 AM
@andrew.bowman the feature @kevin.urban solution has that I was particularly after is that d.name is a key in the map. Using map projection we end up with
{name:"organisation",
properties:}
rather than
{organisation:[properties]}
11-28-2018 02:16 AM
In that case you'll want to keep apoc.map.fromValues()
at the very end:
RETURN collect(apoc.map.fromValues([d.name, properties])) as entityDefs
11-28-2018 11:15 AM
Hi @andrew.bowman -- Still learning here, so I have a question: Wouldn't your solution give back 2 rows of maps instead of just one map? I would test it myself, but not fully sure how @taffyb re-did their data set.
I ask because originally tinkered with some similar approaches, but found I had to use that apoc.map.merge statement to make it come out like requested:
{Person:[{name:"givenName",label:"Given Name",length:35},
{name:"surname",label:"Surname",length:35},
{name:"dob",label:"Date of Birth",format:"dd-mm-yyyy"}],
Organisation:[{name:"name",label:"Name",length:40},
{name:"established",label:"Established",format:"dd-mm-yyyy"}]
}
11-28-2018 11:37 AM
You're partially correct. My solution followed taffyb's approach of returning a collection of maps, so it will return a single row of a list of map values, one element per :EntityDef node.
In order to return back just a single map, instead of a list of maps, then yes you'd need to use apoc.map.merge()
as in your suggested solution.
11-28-2018 11:48 AM
The nodes that are used in the final solution are
CREATE (e1:EntityDef{name:"Person"})
MERGE (e1)-[:HAS_A]->(p1:Property{name:"givenName",label:"Given Name",length:35})
MERGE (e1)-[:HAS_A]->(p2:Property{name:"surname",label:"Surname",length:35})
MERGE (e1)-[:HAS_A]->(p3:Property{name:"dob",label:"Date of Birth",format:"dd-mm-yyyy"})
CREATE (e2:EntityDef{name:"Organisation"})
MERGE (e2)-[:HAS_A]->(p4:Property{name:"name",label:"Name",length:40})
MERGE (e2)-[:HAS_A]->(p5:Property{name:"established",label:"Established",format:"dd-mm-yyyy"})
CREATE (t1:PropertyType{name:"text"})
CREATE (t2:PropertyType{name:"date"})
MERGE (p1)-[:OF]->(t1)
MERGE (p2)-[:OF]->(t1)
MERGE (p3)-[:OF]->(t2)
MERGE (p4)-[:OF]->(t1)
MERGE (p5)-[:OF]->(t2)
the final cypher is
MATCH (d:EntityDef)
OPTIONAL MATCH (d)-[:HAS_A]->(p:Property)-[:OF]->(t:PropertyType)
WITH d,collect(p {.*, type:t.name}) as properties
RETURN apoc.map.mergeList(collect(apoc.map.fromValues([d.name, properties]))) as entityDefs
result:
{
"Organisation": [
{ "format": "dd-mm-yyyy", "name": "established", "label": "Established", "type": "date" },
{ "name": "name", "length": 40, "label": "Name", "type": "text" }
],
"Person": [
{ "format": "dd-mm-yyyy", "name": "dob", "label": "Date of Birth", "type": "date" },
{ "length": 35, "name": "surname", "label": "Surname", "type": "text" },
{ "length": 35, "name": "givenName", "label": "Given Name", "type": "text" }
]
}
All the sessions of the conference are now available online