8

I'm trying to keep items in my table unique based on a combination of two different columns.

I have an instanceId and imageId column (along with others) and based on a couple of posts on Stackoverflow and AWS Forums the below should work?

public void saveUnique(Server server) {
    DynamoDBSaveExpression saveExpression = new DynamoDBSaveExpression();
    Map<String, ExpectedAttributeValue> expectedAttributes =
        ImmutableMap.<String, ExpectedAttributeValue>builder()
            .put("instanceId", new ExpectedAttributeValue(false))
            .put("imageId", new ExpectedAttributeValue(false))
            .build();
    saveExpression.setExpected(expectedAttributes);
    saveExpression.setConditionalOperator(ConditionalOperator.AND);
    try {
        mapper.save(server, saveExpression);
    } catch (ConditionalCheckFailedException e) {
        //Handle conditional check
    }
}

However every time I try and save a duplicate item (same instanceId and imageId) it's successfully being saved into the database.

Am I missing anything else here?

EDIT R.E notionquest answer

Updating to the answer below.

I have a job that runs once a minute polling an API. The response from the API is represented as a Server POJO. The Server has an attribute named instanceId.

I want to make sure that if a Server with that instanceId is already in the database, don't save it.

The Server object has another attribute of id which is set as the table primary key.

public void saveUnique(Server server) {
    DynamoDBSaveExpression saveExpression = new DynamoDBSaveExpression();

    Map<String, ExpectedAttributeValue> expected = new HashMap<>();
    expected.put("instanceId", new ExpectedAttributeValue(new AttributeValue(server.getInstanceId())).withComparisonOperator(ComparisonOperator.NE));
    saveExpression.setExpected(expected);

    try {
        mapper.save(server, saveExpression);
    } catch (ConditionalCheckFailedException e) {
        LOGGER.info("Skipped saving as not unique...");
    }
}

This code will save the Server object over and over again with the exception never being thrown.

The Server POJO

@DynamoDBTable(tableName = "Servers")
public class Server {

    @Id
    private String id = UUID.randomUUID().toString();

    @DynamoDBTypeConvertedJson
    private Task task;

    @DynamoDBAttribute(attributeName = "instanceId")
    private String instanceId;

    public Server() {
    }

    @DynamoDBHashKey
    public String getId() {
        return id;
    }

    // other standard getters and setters
}
Community
  • 1
  • 1
Chris
  • 3,437
  • 6
  • 40
  • 73
  • Probably, it may not be saving anything in the database. The catch block should get executed and method should end normally without any error. Did you check the database whether two different items exist for the same instanceId and imageId. Have they defined as partition key and sort key of the table? – notionquest Mar 16 '17 at 09:39
  • 1
    I've worked out that my code above works if my partition key is set as the instanceId. However it doesn't work if I have something else (like a generic id) set as the partition key. It must be possible to check if two objects are equal without having to use the partition key though, surely? – Chris Mar 16 '17 at 17:40

2 Answers2

6

2019 update

In 2019, looks like nothing has changed here since the time when the question was asked. Providing uniqueness for a non-primary-key field is still tricky in DynamoDB. Here is an article posted on Amazon recently: https://aws.amazon.com/blogs/database/simulating-amazon-dynamodb-unique-constraints-using-transactions/

Basically, instead of using an additional table as described below, the author proposes creating auxiliary records in the same table.

For example, on the below picture, for ensuring that instance_id=2c5d0cc8d900 is a unique value, you have to add a record with an artificial primary key value "instance_id#2c5d0cc8d900". If the insert succeeds, you can insert the main record itself.

+--------------------------+-------------------------+--------+----------------
|           id             |       instance_id       | attr1  | other fields... 
|                          |                         |        |
|      (primary key)       |     (a non-key field,   |        |
|                          |     must be unique)     |        |
+--------------------------+-------------------------+--------+----------------
| instance_id#2c5d0cc8d900 |                         |        |
| a9fd702a                 | 2c5d0cc8d900            | qwerty | ...

While this approach may work fine, I personally still prefer to use a separate table like described below in my original answer. Because, when reading data from a table that contains such auxiliary records, you have to care about filtering them from actual ones.


Original answer

If I correctly understood the question, you'd like to ensure uniqueness for a field which is not the hash key.

(I'm not sure why you do not use instanceId as the hash key for Servers table, I guess you have a reason for that).

My answer: looks like you can't do that without using an auxiliary table.

Here is your existing Servers table:

+----------------------------------------------+
|                 Servers                      |   
+----------------------------------------------+
| * id            the hash key                 |
| * instanceId    non-key field, must be unique|
|                                              |
| * ...                                        |
| * other fields                               |
| * ...                                        | 
+----------------------------------------------+

I would create an additional table with instanceId as the hash key:

+----------------------------------------------+
|                 Instance                     |   
+----------------------------------------------+
| * instanceId    the hash key                 |
+----------------------------------------------+

Having such a table, before saving a record into Servers, you have to ensure first that instanceId value is unique by adding a record (putItem) into Instance, providing a ConditionExpression like attribute_not_exists(instanceId).

And only if the put operation completes without an ConditionalCheckFailedException error, you can proceed with adding the record into Servers.

If you'd like to ensure uniqueness of records in Servers based on combination of two fields, instanceId and imageId, instead of just instanceId use concatenated values of these fields as a single field in your aux table:

+----------------------------------------------+
|               Instance_Image                 |   
+----------------------------------------------+
| * instanceId_imageId   the hash key          |
+----------------------------------------------+
xtx
  • 4,306
  • 4
  • 28
  • 30
  • not sure why there that limitation. in any case if it is primary key one can not create duplicated item with the same primary key.. why creating conditions on first place. 3rd table is too much.. why there is not just NE to whatever field I want to use. I may create an index for this filed. not being limited to use only primary key.. that what I mean. – ses Apr 24 '18 at 15:39
0

The below code checks whether the attribute value is NOT equal to "somevalue" and updates the item if the condition is satisfied.

Map<String, ExpectedAttributeValue> expected = new HashMap<>();     
expected.put("yourattribute", new ExpectedAttributeValue(new AttributeValue("yourvalue")).withComparisonOperator(ComparisonOperator.NE));
notionquest
  • 37,595
  • 6
  • 111
  • 105
  • 1
    Unfortunately this also only seems to work if `"yourattribute"` is the primary key. If it's another random attribute, it still writes it to the database. – Chris Mar 16 '17 at 20:43
  • No, I have tested with non key attribute. The catch block got executed. Try exactly what I have given. Delete the AND condition if you are using only one attribute. If it doesnt work, please explain what you are trying to achieve exactly without much abstraction. – notionquest Mar 16 '17 at 21:41
  • Question updated, hopefully further clarifying things. – Chris Mar 16 '17 at 22:00
  • Updated as requested; – Chris Mar 16 '17 at 22:14
  • did not work for me. still no exception if yourattribute in not primary key. created: https://stackoverflow.com/questions/50005071/dynamodb-conditional-write-check-by-the-filed-if-notequal-ne – ses Apr 24 '18 at 15:29