10

Currently, I'm checking if the item exists with a query and then I'm using put or updateItem, I want to change it and make a single call to DDB. I want to make a query that will update or create item.

Here is a example of my item:

{
   id: 'dsfadsf'
   fa: { "apple" : { "S" : "76-100" }, "yolo" : { "S" : "0-25" }
   pa: { "finish" : { "BOOL" : false },    "userKey" : { "S" : "3e299e12-9e66" }  }
   createdAt: 32432432423
}

item types:

createdAt - Number
fa - Map
pa - Map
id - String
finish - Boolean
key - String

If item exists I want to push a new item like {papa: 'dsfadsf'} (never modify existing item inside fa Map) and modify finish value inside pa Map

This is the item after the update:

{
   id: 'dsfadsf'
   fa: { "apple" : { "S" : "76-100" }, "yolo" : { "S" : "0-25" }, "papa": { "S" : "dsfadsf"} }
   pa: { "finish" : { "BOOL" : true },    "userKey" : { "S" : "3e299e12-9e66" }  }
   createdAt: 32432432423
}

Here is what I tried and its not working

{
    TableName: tableName,
    Key: {
      id: "dsfadsf",
    },
    UpdateExpression: `SET #id = :id, fa.${itemName} = if_not_exists(fa.${itemName}, :text), pa.finish = if_not_exists(pa.finish, :finishval), #ca = :ca`,
    ExpressionAttributeNames: {
      "#id": "id",
      "#ca": createdAt
    },
    ExpressionAttributeValues: {
      ":id": "7fd9a81b-7a7c-4cfb-9c84-25dc2798a8f7",
      ":text": itemText,
      ":finishval": true,
      ":ca": 32432432423
    },
    ConditionExpression: `attribute_not_exists(id)`,
  };
4
  • 1
    try this ConditionExpression: 'id = : id OR attribute_not_exists(id)' Commented Jul 10, 2020 at 22:17
  • 1
    I'm getting an error: "One or more parameter values were invalid: Cannot update attribute id. This attribute is part of the key" Commented Jul 10, 2020 at 22:32
  • 1
    This might be one of the reasons. Are the names and types of attributes same? Commented Jul 11, 2020 at 4:04
  • 1
    They are not the same Commented Jul 11, 2020 at 10:57

1 Answer 1

7

TLDR

This isn't possible with the current structure of your item. Change fa & pa to be string sets, get rid of finish: true & use the ADD update expression.


This is not possible with the current structure of your item. Here's why — To meet your requirement, UpdateExpression needs to be of the form:

if "fa" exists, add {"papa":"dsfadsf"} to it
else, create new "fa"={"papa":"dsfadsf"}

Of all the DynamoDB update expressions, only SET & ADD can be used in this scenario.

The above if else condition cannot be expressed in an UpdateExpression with SET due to the following reasons:

  • SET fa.papa = 'dsfadsf' will update fa if it exists, but if it doesn't, you get the error The document path provided in the update expression is invalid for update.

  • SET fa = {"papa":"dsfadsf"} will create new fa but overwrite it if it exists.

If you try to combine ADD & SET to achieve the above if else condition, into something like ADD fa {} SET fa.papa = dsfadsf, you get the error Two document paths overlap with each other; must remove or rewrite one of these paths

So we're only left with the ADD expression now, but ADD only works on sets. So if you can afford to turn the contents of fa & pa into sets of strings, you can achieve your goal of "create or update in 1 go". Here's how it'll work:

The original item structure has to be:

{
  "ca": 32432432423,
  "fa": [
    "apple:76-100",
    "yolo:0-25"
  ],
  "id": "dsfadsf",
  "pa": [
    "key:9e66"
  ]
}

In DynamoDB JSON, that's:

{
  "ca": {
    "N": "32432432423"
  },
  "fa": {
    "SS": [
      "apple:76-100",
      "yolo:0-25"
    ]
  },
  "id": {
    "S": "dsfadsf"
  },
  "pa": {
    "SS": [
      "key:9e66"
    ]
  }
}

Now, using the following code:

let AWS = require("aws-sdk")
let docClient = new AWS.DynamoDB.DocumentClient()
docClient.update({
    TableName: "my-table",
    Key: { id: "dsfadsf" },
    UpdateExpression: `ADD fa :fa, pa :pa SET ca = :ca`,
    ExpressionAttributeValues: {
        ":fa": docClient.createSet(["papa:dsfadsf"]),
        ":pa": docClient.createSet(["finished"]),
        ":ca": 32432432423
    }
}

If an item with the id "dsfadsf" already exists, it's updated as follows:

{
  "ca": {
    "N": "32432432423"
  },
  "fa": {
    "SS": [
      "apple:76-100",
      "papa:dsfadsf",
      "yolo:0-25"
    ]
  },
  "id": {
    "S": "dsfadsf"
  },
  "pa": {
    "SS": [
      "finished",
      "key:9e66"
    ]
  }
}

If an item with the id "dsfadsf" does NOT exist, it's created as follows:

{
  "ca": {
    "N": "32432432423"
  },
  "fa": {
    "SS": [
      "papa:dsfadsf"
    ]
  },
  "id": {
    "S": "dsfadsf"
  },
  "pa": {
    "SS": [
      "finished"
    ]
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

how would this behave with concurrent updates to a string set, so in this case to pa or fa. Would all the updates be preserved?
Exellent answer. Very efficient to do those updates in a single transaction.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.