AWS Developer Tools Blog
Amazon DynamoDB Document API in Ruby (Part 2 – Condition Expressions)
As we showed in the previous post, it’s easy to put JSON items into Amazon DynamoDB and retrieve specific attributes with projection expressions. Condition Expressions provide a more flexible and SQL-like way to retrieve only the items you want from DynamoDB. First, let’s put a few more items into DynamoDB using a BatchWriteItem operation. (Note: this code uses the same ProductCatalog table we used in Part 1)
# add some more items @dynamodb.batch_write_item( :request_items => { "ProductCatalog" => [ {:put_request => { :item => { Id: 300, Title: "Sojourner", Description: "Mars Pathfinder robotic Mars rover", Price: BigDecimal.new("2.65e8"), LaunchDate: { M: 12, D: 4, Y: 1996 }, LostCommunicationDate: { M: 9, D: 27, Y: 1997 }, Features: { Rover: true, }, NumberInStock: 10, OrdersPlaced: 3, Tags: ["#Mars", "#InStarTrekSeason4", "#InRedPlant2000", "#LostComms"], }}}, {:put_request => { :item => { Id: 301, Title: "Spirit", Description: "Mars Exploration Rover – A", Price: BigDecimal.new("4.1e8"), LaunchDate: { M: 6, D: 10, Y: 2003 }, LostCommunicationDate: { M: 3, D: 22, Y: 2010 }, Features: { Rover: true, }, NumberInStock: 10, OrdersPlaced: 5, Tags: Set.new(["#Mars", "#StuckOnMars", "#LostComms"]), }}}, {:put_request => { :item => { Id: 302, Title: "Opportunity", Description: "Mars Exploration Rover – B", Price: BigDecimal.new("4.1e8"), LaunchDate: { M: 7, D: 7, Y: 2003 }, LostCommunicationDate: nil, Features: { Rover: true, }, NumberInStock: 10, OrdersPlaced: 10, Tags: Set.new(["#Mars", "#StillRoving"]), }}}, {:put_request => { :item => { Id: 303, Title: "Curiosity", Description: "car-sized robotic rover", Price: BigDecimal.new("2.5e9"), LaunchDate: { M: 11, D: 26, Y: 2011 }, LostCommunicationDate: nil, Features: { Rover: true, RoboticArm: true, }, NumberInStock: 0, OrdersPlaced: 30, Tags: Set.new(["#Mars", "#MarsCuriosity", "#StillRoving"]), }}}, ] } )
Using Condition Expressions
We could also use condition expressions on the results of Query, but since we’re using a simple data model (only have hash key on product Id), we demonstrate this with scans. We use the following helper method to perform the scan and format the product titles returned:
def do_scan(filter_exp, exp_attribute_values) result = @dynamodb.scan( :expression_attribute_values => exp_attribute_values, :filter_expression => filter_exp, # Condition Expressions are supplied through the FilterExpression parameter :projection_expression => "Title", :table_name => "ProductCatalog" ).data.items # format all retrieved titles into a single line return "scan retrieved: #{(result.map { |item| item["Title"] }).join(", ")}" end
Let’s look at some example expressions and the results they return from our current ProductCatalog table:
# All products that don't have a launch month of November (11) puts do_scan( "LaunchDate.M <> :m", { ":m" => 11 } ) # scan retrieved: 20-Bicycle 205, Opportunity, Spirit, Sojourner # All rover products that don't have a launch month of November puts do_scan( "attribute_exists(Features.Rover) AND LaunchDate.M <> :m", { ":m" => 11, } ) # scan retrieved: Opportunity, Spirit, Sojourner # Non-rovers puts do_scan( "attribute_not_exists(Features.Rover)", nil ) # scan retrieved: 20-Bicycle 205 # mid-range rovers or inexpensive products puts do_scan( "(Price BETWEEN :low AND :high) OR Price < :verylow", { ":verylow" => BigDecimal.new("1e8"), ":low" => BigDecimal.new("3e8"), ":high" => BigDecimal.new("5e8") } ) # scan retrieved: 20-Bicycle 205, Opportunity, Spirit # within-Item referencing: more orders placed than in stock puts do_scan( "OrdersPlaced > NumberInStock", nil ) # scan retrieved: Curiosity # string prefixing puts do_scan( "begins_with(Title, :s)", { ":s" => "S", } ) # scan retrieved: Spirit, Sojourner # contains puts do_scan( "contains(Tags, :tag1) AND contains(Tags, :tag2)", { ":tag1" => "#StuckOnMars", ":tag2" => "#LostComms", } ) # scan retrieved: Spirit # contains (Note: "Tags" is a list for Sojourner) puts do_scan( "contains(Tags, :tag1)", { ":tag1" => "#LostComms", } ) # scan retrieved: Spirit, Sojourner # in operator puts do_scan( "Id in (:id1, :id2)", { ":id1" => 302, ":id2" => 303, } ) # scan retrieved: Curiosity, Opportunity # equivalently, with parentheses puts do_scan( "(Id = :id1) OR (Id = :id2)", { ":id1" => 302, ":id2" => 303, } ) # scan retrieved: Curiosity, Opportunity
Next Steps
As you can see, condition expressions enable you to write more concise code to retrieve data. They also provide querying capabilities unavailable with the original access model such as within-Item references and more flexible conditions with parentheses. In an upcoming blog post, we’ll take a closer look at how we can update existing data through update expressions.