122

Is it possible to ORDER results with Query or Scan API in DynamoDB?

I need to know if DynamoDB has something like ORDER BY 'field' from SQL queries?

Thanks.

Yves M.
  • 29,855
  • 23
  • 108
  • 144
Samuel Negru
  • 1,321
  • 2
  • 8
  • 3

7 Answers7

65

You can use the sort-key and apply the ScanIndexForward parameter in a query to sort in either ascending or descending order. Here I limit items returned to 1.

var params = {
    TableName: 'Events',
    KeyConditionExpression: 'Organizer = :organizer',
    Limit: 1,
    ScanIndexForward: false,    // true = ascending, false = descending
    ExpressionAttributeValues: {
        ':organizer': organizer
    }
};

docClient.query(params, function(err, data) {
    if (err) {
        console.log(JSON.stringify(err, null, 2));
    } else {
        console.log(JSON.stringify(data, null, 2));
    }
});
helvete
  • 2,455
  • 13
  • 33
  • 37
kometen
  • 6,536
  • 6
  • 41
  • 51
  • 11
    Problem is if you want to return *all* items. Essentially that means you have to create a new dummy column, assign the same value in it to all rows, create a GSI on that column, and call query instead of scan. – JHH Jun 02 '16 at 08:42
  • what if i want to return based on some non-key field? like created_on number field – Yusuf Sep 16 '20 at 09:41
  • 1
    Then you might want to get all records and then filter those using javascript or similar. DynamoDB is basically a key-value-store with limited functionality. But is very fast when you can use the key(s). – kometen Sep 16 '20 at 12:06
  • You´re wrong on your explanation, Limit does not Limit the items returned, but, Items scanned. – Igor Quirino Jun 20 '23 at 14:06
53

Not explicitly, however, ordering is obviously needed for many real world use cases and can be modeled by means of the Hash and Range Type Primary Key accordingly:

In this case, the primary key is made of two attributes. The first attributes is the hash attribute and the second one is the range attribute. Amazon DynamoDB builds an unordered hash index on the hash primary key attribute and a sorted range index on the range primary key attribute. [emphasis mine]

You can then use this range index to optionally request items via the RangeKeyCondition parameter of the Query API and specify forward or backward traversal of the index (i.e. the sort direction) via the ScanIndexForward parameter.

Update: You can order by an attribute with a local secondary index in the same way.

Vitalii Fedorenko
  • 110,878
  • 29
  • 149
  • 111
Steffen Opel
  • 63,899
  • 11
  • 192
  • 211
  • 21
    The ScanIndexForward param seems to only apply to [Query](http://docs.amazonwebservices.com/amazondynamodb/latest/developerguide/API_Query.html), not [Scan](http://docs.amazonwebservices.com/amazondynamodb/latest/developerguide/API_Scan.html) correct? How would one return an ordered paginated list of all items in a table using Query? Scan seems to be the way to return "*", but it does not seem to have a param for ordering results. – case2000 Feb 15 '12 at 20:20
  • I've not used this feature, only read about it, but Query supports specifying a [Limit](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html#ScanQueryLimit), to cap the number of results received, and if there are more items that match your query when the limit is reached, you'll receive a _LastEvaluatedKey_ that can be used to do another query and keep retrieving results. – fernio Dec 10 '14 at 15:59
  • 2
    Important gotcha: the returned results won't actually be sorted. The sorting only comes into play if you apply a 'Limit' value, or the number of items exceeds the 1MB lookup limit. E.g., you might have 5 records, with partition key 'p1', and sort keys: ['b', 'd', 'a', 'c', 'e']. If you perform a query for just 'p1', you'll receive ['b', 'd', 'a', 'c', 'e']. But if you specify a Limit of 2, it will return ['b', 'a'] – jameslol Feb 22 '19 at 00:19
  • Snippet below from [AWS docs][1]. `ScanIndexForward = true` is `asc` and `false` is `desc`. FYI sort key is same as range key. > Query results are always sorted by the sort key value. If the data type of the sort key is Number, the results are returned in numeric order; otherwise, the results are returned in order of UTF-8 bytes. By default, the sort order is ascending. To reverse the order, set the ScanIndexForward parameter to false. [1]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html – Given Jan 09 '23 at 16:38
12

Use ScanIndexForward(true for ascending and false for descending) and can also limit the result using setLimit value of Query Expression.

Please find below the code where used QueryPage for finding the single record.

    public void fetchLatestEvents() {
        EventLogEntitySave entity = new EventLogEntitySave();
        entity.setId("1C6RR7JM0JS100037_contentManagementActionComplete");
        
        DynamoDBQueryExpression<EventLogEntitySave> queryExpression = new DynamoDBQueryExpression<EventLogEntitySave>().withHashKeyValues(entity);
        queryExpression.setScanIndexForward(false);
        queryExpression.withLimit(1);
        queryExpression.setLimit(1);
        
        List<EventLogEntitySave> result = dynamoDBMapper.queryPage(EventLogEntitySave.class, queryExpression).getResults();
        System.out.println("size of records = "+result.size() );
    }
        
    @DynamoDBTable(tableName = "PROD_EA_Test")
    public class EventLogEntitySave {
        
            @DynamoDBHashKey
            private String id;
            private String reconciliationProcessId;
            private String vin;
            private String source;
    }
        
    public class DynamoDBConfig {
        @Bean
        public AmazonDynamoDB amazonDynamoDB() {
        
                String accesskey = "";
                String secretkey = "";
                //
                // creating dynamo client
                BasicAWSCredentials credentials = new BasicAWSCredentials(accesskey, secretkey);
                AmazonDynamoDB dynamo = new AmazonDynamoDBClient(credentials);
                dynamo.setRegion(Region.getRegion(Regions.US_WEST_2));
                return dynamo;
            }
        
        @Bean
        public DynamoDBMapper dynamoDBMapper() {
            return new DynamoDBMapper(amazonDynamoDB());
        }
    }
Jasper Citi
  • 1,673
  • 1
  • 23
  • 31
ABHAY JOHRI
  • 1,997
  • 15
  • 19
5

I never thought that such a trivial task could turn into a problem in DynamoDB. Dynamo requires some basic partition. I managed to order data by adding an extra column status and then create GSI index using both fields. I order data with status="active" by createdAt field.

Create GSI

{
        IndexName: "createdAt",
        KeySchema: [
            { AttributeName: "status", KeyType: "HASH" },
            { AttributeName: "createdAt", KeyType: "RANGE" }
        ],
        Projection: { ProjectionType: "ALL" },
        ProvisionedThroughput: {
          ReadCapacityUnits: N,
          WriteCapacityUnits: N
        }
      }

query data

const result = await this.dynamoClient.query({
  TableName: "my table",
  IndexName: "createdAt",
  KeyConditionExpression: "#status = :status and #createdAt > :createdAt",
  Limit: 5,
  ExpressionAttributeValues: {
    ":status": {
      "S": "active"
    },
    ":createdAt": {
      "S": "2020-12-10T15:00:00.000Z"
    }
  },
  ExpressionAttributeNames: {
    "#status": "status",
    "#createdAt": "createdAt"
  },
});
radzserg
  • 1,258
  • 1
  • 13
  • 22
  • btw, it only works for me if I do it this way ExpressionAttributeValues: { ":status": "active", ":createdDate": "2020-12-10T15:00:00.000Z" } – yk1007 Mar 14 '23 at 17:51
  • https://stackoverflow.com/questions/43678636/dynamodb-query-incorrect-operand-type – yk1007 Mar 14 '23 at 17:52
4

Another option which should solve the problem is to

  1. Define a local secondary index with the "normal" hash key to be the hash key of the LSI as well
  2. Define the field you want to sort as "Sort Key" of the LSI
  3. Query the LSI and set the ordering as desired (see above)

This will enable sorting of any value of your table as required. It is a very efficient way to find the highest ranking items in your table without the need to get the whole query and then filtering it afterwards.

tcb
  • 2,745
  • 21
  • 20
Geole
  • 356
  • 1
  • 5
  • 17
  • What above? If the normal hash is an unorderd for the sort generated id then including it doesn't seem to work. Am I missing something? – Samantha Atkins Aug 14 '19 at 17:40
3

If you are using boto2 and you have the sort key on one of the columns in your table, you can sort what you retrieve in order or in reverse order by saying:

result = users.query_2(
    account_type__eq='standard_user',
    reverse=True)

If you are using boto3 and you have the sort key on the column that you want to sort the result by, you can sort the data you retrieve by saying:

result = users.query(
    KeyConditionExpression=Key('account_type').eq('standard_user'),
    ScanIndexForward=True)

Remember in boto3 if ScanIndexForward is true , DynamoDB returns the results in the order in which they are stored (by sort key value). This is the default behavior. If ScanIndexForward is false, DynamoDB reads the results in reverse order by sort key value, and then returns the results to the client.

pilatipus
  • 91
  • 1
  • 4
0

If the table already existed then add GSI (Global Secondary Index) to the attribute you want for the table and use Query, not Scan. If you are about to create the table then you can add LSI (Local Secondary Index) to the attribute you want.

Sharhabeel Hamdan
  • 1,273
  • 13
  • 15