简体   繁体   中英

Dynamodb scan() using FilterExpression

First post here on Stack and fairly new to programming with Python and using DynamoDB, but I'm simply trying to run a scan on my table that returns results based on two pre-defined attributes.

---Here is my Python code snippet---

shift = "3rd"
date = "2017-06-21"

if shift != "":
    response = table.scan(
        FilterExpression=Attr("Date").eq(date) and Attr("Shift").eq(shift)
    )

My DynamoDB has 4 fields.

  1. ID
  2. Date
  3. Shift
  4. Safety

DynamoDB 图片

Now for the issue, upon running I'm getting two table entries returned when I should only be getting the first entry... the one with "No safety issues" based on my scan criteria.

---Here is my DynamoDB return results---

[
  {
    "Shift": "3rd",  
    "Safety": "No safety issues",  
    "Date": "2017-06-21",
    "ID": "2"
  }, 
  {
    "Shift": "3rd", 
    "Safety": "Cut Finger", 
    "Date": "2017-06-22", 
    "ID": "4"
  }
]

Items Returned: 2

I believe that by applying the FilterExpression with the logical 'and' specified that the scan operation is looking for entries that meet BOTH criteria since I used 'and'.

Could this be because the 'shift' attribute "3rd" is found in both entries? How do I ensure it returns entries based on BOTH criteria being meet and not just giving me results from one attribute type?

I have a feeling this is simple but I've looked at the available documentation at: http://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.Table.scan and am still having trouble. Any help would be greatly appreciated!

PS I tried to keep the post simple and easy to understand (not including all my program code) however, if additional information is needed I can provide it!

This is because you used Python's and keyword in your expression, instead of the & operator.

If a and b are both considered True , a and b returns the latter, b :

>>> 2 and 3
3

If any of them is False , or if both of them are, the first False object is returned:

>>> 0 and 3
0
>>> 0 and ''
0
>>> 

The general rule is, and returns the first object that allows it to decide the truthiness of the whole expression.

Python objects are always considered True in boolean context. So, your expression:

Attr("Date").eq(date) and Attr("Shift").eq(shift)

will evaluate as the last True object, that is:

Attr("Shift").eq(shift)

which explains why you only filtered on the shift.

You need to use the & operator. It usually means "bitwise and" between integers in Python, it is redefined for Attr objects to mean what you want: "both conditions".

So you must use the "bitwise and":

FilterExpression=Attr("Date").eq(date) & Attr("Shift").eq(shift)

According to the documentation ,

You are also able to chain conditions together using the logical operators: & (and), | (or), and ~ (not).

Dynamodb scan() using FilterExpression

For multiple filters, you can use this approach:

import boto3
from boto3.dynamodb.conditions import Key, And

filters = dict()
filters['Date'] = "2017-06-21"
filters['Shift'] = "3rd"

response = table.scan(FilterExpression=And(*[(Key(key).eq(value)) for key, value in filters.items()]))

Using parts from each of the above answers, here's a compact way I was able to get this working:

from functools import reduce
from boto3.dynamodb.conditions import Key, And

response = table.scan(FilterExpression=reduce(And, ([Key(k).eq(v) for k, v in filters.items()])))

Allows filtering upon multiple conditions in filters as a dict . For example:

{
    'Status': 'Approved', 
    'SubmittedBy': 'JackCasey'
}

Expanding on Maxime Paille's answer, this covers the case when only one filter is present vs many.

from boto3.dynamodb.conditions import And, Attr
from functools import reduce
from operator import and_

filters = dict()
filters['Date'] = "2017-06-21"
filters['Shift'] = "3rd"

table.scan("my-table", **build_query_params(filters))

def build_query_params(filters):
    query_params = {}
    if len(filters) > 0:
        query_params["FilterExpression"] = add_expressions(filters)

    return query_params

def add_expressions(self, filters: dict):
    if filters:
        conditions = []
        for key, value in filters.items():
            if isinstance(value, str):
                conditions.append(Attr(key).eq(value))
            if isinstance(value, list):
                conditions.append(Attr(key).is_in([v for v in value]))
        return reduce(and_, conditions)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM