Modifying specific elements of an array

Hi everyone,

I have a question concerning arrays in cypher.

Say, I have a node with an array:

CREATE (n:Node {array: [1,2,3,4,5,6]})

Now I want to write on the second and fifth entry of this array. From other languages, I would guess something like

MATCH (n:Node) SET n.array[1] = 10, n.array[4] = 20

However this does not work in cypher. I get a Neo.ClientError.Statement.SyntaxError with the message

Invalid input '[': expected an identifier character, '.', whitespace, '(' or '=' (line 1, column 27 (offset: 26))

I know several ways of how to circumvent this problem by unwinding the array and assigning it anew. So, I am not interested in ways to make it work at all. I would just like to know, if there is a straight-forward way. And if there isn't, why isn't there?

Regards,
Elena

I don't think that you can SET one element of an array at once. You need to set the entire array. It's because array is a property of Node but Node.array[1] doesn't make sense while using with SET.
So, after playing with it for a while I figured out one kinda "straight-forward" way to set specific elements of an array.

MATCH (n:Node) SET n.array= [n.array[0], 10]+n.array[2..4]+[20]+n.array[5..] RETURN n.array

I'm not sure if you'd consider it a straight-forward way but running the above query will give you the expected result.

1 Like

Hi Shashi278,

thanks for your reply! Your solution is actually better than my unwind-solution so far. So, I guess it might help me become a bit leaner in my code. Thanks for that!

Still, I find it confusing why it is not possible to set certain entries of the array directly ;-)

Regards,
Elena

I think the challenge is that we need to replace the array in order to change an element in it.
While Shashi278 was posting I had come up with something very similar

match (n:Node)
set n.array= n.array[0]+[10]+n.array[2..4]+[20]+n.array[5..7]

In Neo4j and Cypher [1,2,3] is not an array, it's a collection.

While Cypher doesn't have anything built-in to mutate a collection, apoc does.

RETURN apoc.coll.set([1,3,5,7,9], 3, 11) AS output
// output: [1,3,5,11,9]

From: Neo4j Labs: 9.3 Collection Functions

@elena.kohlwey's request:

CREATE (n:Node {array: [1,2,3,4,5,6]})

Write on the second and fifth entry of this array. Other languages would be something like...

MATCH (n:Node) SET n.array[1] = 10, n.array[4] = 20

With a desired result of

(n:Node {array: [1,10,3,4,20,6]})

To accomplish that with apoc:

MATCH (n:Node)
SET n.array = apoc.coll.set(n.array, 1, 10)
SET n.array = apoc.coll.set(n.array, 4, 20)
RETURN n
// result: (n:Node {array: [1,10,3,4,20,6]})

I believe this is by design. Think about languages like Python where maps vs arrays are in play and what's immutable. A property in a graph is really a map to the node's state of existence. So, a change in a map on a node is a significant event. IMHO, you don't want to change this. It's good governance around cypher's management of the database. So, your desired code example is great in SQL and dangerous in a graph. Again, that's why you'd want to have cypher update the map NOT just the value. The governance is by using: SET node = {:, :,…}. Non-intuitive in SQL. Spot on in graph theory where you want to add attributes to nodes.

I get the confusion as an array is so often used as a value in many languages. But remember, the fact that ALL node attributes are maps. That's why I can change "Peter" to "Mary" on a name property but not add a 't' to Peter's spelling - because even a name is an "array" of letters. Make sense? When I update the “name”, I'm really updating a map and setting a "location" on that map. And yes, it's more confusing when we see "" but remember, in reality, outside of those are "{ }". I do wish Neo’s docs were more extensive about how and why they do things in cypher as that would help us wrestle with problems like this one.

So yep, that's where APOC comes in. In many cases, APOC helps us pretend this is SQL :blush: But, I'd caution using APOC to make this "easier" because you weaken the value of a graph over a relational table in SQL in the name of convenience. My experiences with this type of requirement led me to use $params to do the manipulation on mapped values and then I map/re-map the node.

Finally, I also believe that devs should leverage various tech for the different challenges. It’s that old “to a hammer everything looks like a nail” adage :blush: E.g., I’ve used SQL and SPs to create very extensive cypher statements which I then run in Neo and visa-versa.

My 0.02.

1 Like

Hi everyone,

thank you very much for your explanations. That really helped me get further!

Thanks!
Elena

+1 from me. That's worth a careful re-read.

There's one other thing you can do. While it's a bit of a slog to started, once you're rolling, it would likely only take a couple hours to write your own procedure for things like this.

CREATE (n:Node {array: [1,2,3,4,5,6]})
CALL myprocs.arrayResetA(n, 10, 20) yield node as n
RETURN n
// (n:Node {array: [1,10,3,4,20,6]})