Search code examples
neo4jcypher

How do I remove objects containing NULL from a COLLECT() clause in a CYPHER query?


I've run into the following issue with this Cypher query. The overall results are returned in an array of objects, as intended. However, the collect() clause is giving me some trouble.

If user has no myFriends and no theirFriends, an empty array should be returned. Otherwise, if myFriends and/or theirFriends has values, it should be a single array of objects of the combined friends, with the id property of the respective friend.

Query:

MATCH (user:User)
WHERE user.id IN ['1', '2', '3']
OPTIONAL MATCH (user)-[:HAS_FRIEND]->(myFriends:User)
OPTIONAL MATCH (user)<-[:HAS_FRIEND]-(theirFriends:User)
OPTIONAL MATCH (user)-[:HAS_TEACHER]->(myTeachers:User)
RETURN {
  name: user.name,
  friends: collect({id: myFriends.id}) + collect({id: theirFriends.id}),
  teachers: collect({id: myTeachers.id})
}

Results in:

[
  {
    name: 'Joe',
    friends: [{id: null}, {id: null}],
    teachers: [{id: null}]
  }, ...
]

Desired result:

[
  {
    name: 'Joe',
    friends: [],
    teachers: []
  }, {
    name: 'Jen',
    friends: [{id: '4'}, {id: '6'}, {id: '7'}],
    teachers: [{id: '8'}, {id: '9'}]
  }
]

Solution

  • [UPDATED]

    A really nice thing about your particular data model is: you can get all the myFriends and theirFriends elements with a single undirected relationship pattern: (user)-[:HAS_FRIEND]-(f).

    For example:

    MATCH (user:User)
    WHERE user.id IN ['1', '2', '3']
    OPTIONAL MATCH (user)-[:HAS_FRIEND]-(f:User)
    OPTIONAL MATCH (user)-[:HAS_TEACHER]->(t:User)
    RETURN {
      name: user.name,
      friends: COLLECT(DISTINCT f{.id}),
      teachers: COLLECT(t{.id})
    }
    

    So, no list concatenation is needed. Also, the DISTINCT option removes duplicate friends elements.

    By the way, this query also uses the neat map projections syntax as suggested by @P.S., which avoids null values.