DynamoDB condition expression query

0

Use Case

I have a table has below schema:

UserId(hashkey), earnedPoints, status

I want to achieve: At any given time, for the same seller, he/she should only have 1 PENDING status record. Example:

  • Insert user1, 10, PENDING -> successful, as db dose not have record for user1
  • Insert user1, 30, PENDING -> condition fail, as db already contain user1 record and that record is in PENDING status.
  • Insert user2, 10, REDEEMED -> successful, as db dose not have record for user2
  • Insert user2, 10, PENDING -> successful, db has record for user2, but that record is in status of REDEEMED, so inserting PENDING record is allowed.

What I have tried

Try to use DDB Expression: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html to achieve this during putItem operation, using software.amazon.awssdk.enhanced client.

So I wrote:

Expression conditionExpression 
    = Expression.builder()
                .expression("attribute_not_exists(userId) OR #currentStatus <> :pendingStatus")
                .putExpressionName("#currentStatus", "status")
                .putExpressionValue(":pendingStatus", AttributeValue.builder().s("PENDING").build())
                .build();

Does not work.

Any suggestions?

Dawn
asked 3 months ago245 views
3 Answers
1

Hi Dawn - I was able to get the conditional PutItem you're looking for to work with the basic Java v1 SDK as follows....

    private static PutItemRequest createPutItemRequest() {
        PutItemRequest putItemRequest = new PutItemRequest();
        putItemRequest.setTableName("condexpr");
        putItemRequest.setItem(getItem());
        String conditionExpression = "attribute_not_exists(#3a350) Or NOT (#3a351 = :3a351)";
        putItemRequest.setConditionExpression(conditionExpression);
        putItemRequest.setExpressionAttributeNames(getExpressionAttributeNames());
        putItemRequest.setExpressionAttributeValues(getExpressionAttributeValues());
        return putItemRequest;
    }

    private static Map<String, AttributeValue> getItem() {
        Map<String, AttributeValue> item = new HashMap<String, AttributeValue>(); 
        item.put("id", new AttributeValue("user1"));
        item.put("score", new AttributeValue().withN("110"));
        item.put("status", new AttributeValue("REDEEMED"));
        return item;
    }

    private static Map<String, String> getExpressionAttributeNames() {
        Map<String, String> expressionAttributeNames = new HashMap<String, String>(); 
        expressionAttributeNames.put("#3a350", "id");
        expressionAttributeNames.put("#3a351", "status");
        return expressionAttributeNames;
    }

    private static Map<String, AttributeValue> getExpressionAttributeValues() {
        Map<String, AttributeValue> expressionAttributeValues = new HashMap<String, AttributeValue>(); 
        expressionAttributeValues.put(":3a351", new AttributeValue("PENDING"));
        return expressionAttributeValues;
    }

Maybe you could try something like the following?

Expression conditionExpression 
    = Expression.builder()
                .expression("attribute_not_exists(#myId) OR NOT (#currentStatus = :pendingStatus)")
                .putExpressionName("#myId", "userId")
                .putExpressionName("#currentStatus", "status")
                .putExpressionValue(":pendingStatus", AttributeValue.builder().s("PENDING").build())
                .build();
answered 3 months ago
profile pictureAWS
EXPERT
reviewed 3 months ago
  • Hi, thanks for replying! However I tested it the proposed solution myself, it did not work for below case:

    Insert user1, 10, PENDING -> successful, as db dose not have record for user1
    Insert user1, 30, PENDING -> condition fail, as db already contain user1 record and that record is in PENDING status.
    
0
Accepted Answer

Ah, I think I misunderstood your design. In your original message you mentioned that userId is the partition (hash) key for your table, but did not mention a sort key. Now starting to believe that you might be using one of the attributes (perhaps status) as the sort key? I think that if you take an approach where you have a simple primary key (just the partition key attribute userId - no sort key), my proposed solution will work for you. It does not maintain a complete history of the status stages that a given user might go through - instead, updating an existing item in place for that userId if their status changes in a way that meets your set of conditions. Does that make sense?

Using transactions per the blog you linked may be an option, but it's heavy, and I think not justified.

answered 3 months ago
  • Hi, apologize for not being clear in my example, I simplified the issue in my example in an attempt to explain what I want to achieve, but yes, actually I am using a hashkey and a sortKey. I think in this case, it cannot be achieved unless using transactions, but still really appreciate the answers!

0

Interesting - it worked in my testing. Any chance you didn't match the case on "PENDING" ? The expression check is case sensitive.

answered 3 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions