Create GSI on an attribute which has the value of Set - amazon-web-services

So i want to create a simple Dynamodb table called reminders which at the moment has 3 columns :
reminder_id : This is the hash key
reminder_tag: I want to have a global secondary index on this field . But at the same time i want to ensure that the tags attribute should have the datatype of Set . Because there can be multiple tags on a reminder.
reminder_title: I also want to have a global secondary index on this field. This will be a string field.
I checked the documentation : https://boto3.amazonaws.com/v1/documentation/api/latest/reference/customizations/dynamodb.html#valid-dynamodb-types on what are the possible datatypes available in Boto3 .
So i have come up with this script :
import boto3
def create_reminders_table():
"""Just create the reminders table."""
session = boto3.session.Session(profile_name='dynamo_local')
dynamodb = session.resource('dynamodb', endpoint_url="http://localhost:8000")
table = dynamodb.create_table(
TableName='Reminders',
KeySchema=[
{
'AttributeName': 'reminder_id',
'KeyType': 'HASH'
}
],
AttributeDefinitions=[
{
'AttributeName': 'reminder_id',
'AttributeType': 'S'
},
{
'AttributeName': 'reminder_tag',
'AttributeType': 'SS'
},
{
'AttributeName': 'reminder_title',
'AttributeType': 'S'
}
],
GlobalSecondaryIndexes=[
{
'IndexName': 'ReminderTagGsi',
'KeySchema': [
{
'AttributeName': 'reminder_tag',
'KeyType': 'HASH'
}
],
'Projection': {
'ProjectionType': 'INCLUDE',
'NonKeyAttributes': [
'reminder_title'
]
}
},
{
'IndexName': 'ReminderTitleGsi',
'KeySchema': [
{
'AttributeName': 'reminder_title',
'KeyType': 'HASH'
}
],
'Projection': {
'ProjectionType': 'KEYS_ONLY'
}
}
],
BillingMode='PAY_PER_REQUEST'
)
return table
if __name__ == '__main__':
movie_table = create_reminders_table()
print("Table status:", movie_table.table_status)
But when i run this i get the below issue:
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the CreateTable operation: Member must satisfy enum value set: [B, N, S]
I searched and came across this question asked by someone which has the same issue : https://forums.aws.amazon.com/thread.jspa?messageID=613970
Can someone please help me with this since the solution of not providing a datatype either does not work .
Also is it possible to have an index on a an attribute which is of value Set ? I mean i should enable the user to search for reminders with a tag , and for doing that i need to have a set.
Request someone to please help me regarding this.

Is it possible to have an index on an attribute which is of value Set ?
No. As the CreateTable docs say, "the attributes in KeySchema must also be defined in the AttributeDefinitions", to a data type to one of (S)tring, (N)umber or (B)inary."
enable the user to search for reminders with a tag , and for doing that i need to have a set.
A DynamoDB workaround for one-many relations is a composite sort key as in urgent#work. That would only be sensible for a small, fixed number of tags, though.
Your least-bad option is to query by user (and perhaps further narrowing with some sort key), then filtering the results by tag membership outside DynamoDB. (N.B. The IN operator cannot be used in a Query's FilterConditionExpression, so it's of no use to you here).
I want to have a global secondary index on reminder_title
reminder_title is a poor candidate for an index Primary Key. An index's (and table's) Primary Key must ensure per-record uniqueness. A title would likely not. You probably need a combination of 3 elements, user_id, request_id and title, to ensure key uniqueness across records.
Consider a composite Primery Key with, say, user_id for the Partition Key (= HASH) and a compound sort key in a new column (SK) that concatenates title#request_id. You would then search by-user-by-title with:
user_id="Zaphod" AND begins_with(SK, "exercise")

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html
“Each primary key attribute must be a scalar (meaning that it can hold only a single value). The only data types allowed for primary key attributes are string, number, or binary. There are no such restrictions for other, non-key attributes.”

Related

Loopback where filter not working inside include

I have this filter written that i am passing to my endpoint written in Loopbackjs.
const filter = {
where: { status: { neq: "processed" } },
include: [
{ relation: "vendor", scope: { fields: ["id", "name"] } },
{
relation: "pickupLocation",
scope: { where: { cityId: "5ee134da0138634c27a6e1dd" } },
},
],
limit: 200,
skip: 0
};
return this.http.get<IPickupRequest[]>(
`${environment.url}/PickupRequests?filter=${encodeURIComponent(
JSON.stringify(filter)
)}`
);
The pickupRequest collection contains a belongsTo relation to pickupLocation collection. The pickupLocation collection contains a property called cityId on which i am trying to apply a where clause but it does not work. Moreover I also want to get the fields of pickupLocation too inside the pickupRequest object so that i cna use them in my table. The simple include works perfectly and shows the data as desired but when where clause is applied it just doesn't work and also shows only pickupRequest object but does not include pickupLocation data inside it. Moreover since i am using pagination I need it to return me exactly 200 records each time. Am i doing something wrong? What is the exact issue here? I am using Loopback 3
Here, you are expecting that where filter applied on the pickupLocation relation would act something similar to SQL's inner join. However, in loopback, whenever you do an include in the query, the framework internally makes two separate queries on collections or tables of each of those models and combine them; effectively making it work like SQL's left outer join. So, in the above query PickupRequest list will have all object, irrespective of where filter on pickupLocation model. The pickupLocation relation would be present only in those PickupRequest records where cityId = "5ee134da0138634c27a6e1dd" and remaining places, the relation would be an null object.
To overcome this problem, I would say, make your primary query on pickupLocation model as per the { cityId: "5ee134da0138634c27a6e1dd" } where filter and do an include of PickupRequest model.

Is there a way to choose a nested field as a partition key in AWS DynamoDB?

I have a JSON document like:
{
"best_answer": {
"answers": {
"a" :"b",
"c" :"d"
},
"question": "random_question"
},
"blurbs": []
}
And I want to create the partition key on the "question" field (nested inside best_answer). How to do this on the AWS Console?
The only way this is possible is to add the "question" entity as a top level attribute on the item, in this case the partition key, in addition to being embedded in the JSON. Whether that is a good partition key remains to be seen. I cannot comment on that without know more about your use case and its access patterns to start with.

Creating indexes from nested structure in DynamoDB

I wonder if it's possible to create an index that could look like this
{
"dispenserId": "my-dispenser-123", // primary key
"users": ["user5", "user12"],
"robotId": "my-robot-1",
"enabled": true,
"side": left
}
Based on my DynamoDB documents that look like this
{
"robotId": "my-robot-1", // primary key
"dispensers": {
"left": "left-dispenser-123",
"right": "right-dispenser-123",
"users": ["user5", "user12"]
},
"enabled": true,
"users": ["user1", "user32"]
}
I can't figure out how to point at either dispensers.left or dispensers.right and use that as a key, neither can I figure out how to make a side: left/right attribute based on the path of the dispenser ID.
Can it be achieved with the current structure? If not, what document structure would you guys suggest instead. which allows me to hold the same data?
What you are trying to do (use a map element as a key attribute for an index) is not supported by DynamoDB.
The index partition key and sort key (if present) can be any base table attributes of type string, number, or binary. (Source)
You cannot use (an element of) a map attribute as a a key attribute for an index because the key attribute must be a string, number, or binary attribute from the base table.
Consider using the adjacency list design pattern for your data. It will allow you to easily add both the left and right dispensers to your index.
My new structure looks like this
partition key: robotId
sort key: compoundKey
[
{
"robotId": "robot1",
"enabled": true,
"users": [
"user1",
"user3"
],
"compositeKey": "robot--robot1"
},
{
"robotId": "robot1",
"dispenserId": "dispenser1",
"compositeKey": "dispenser--dispenser1",
"side": "left",
"users": [
"user4",
"user61"
]
}
]
Then I have an index with the dispenserId as partition key, so I can either look the dispensers for a given robot (using the table) or look up details about a dispenser (using the index)

DynamoDB Search with HashKey

I have table with the following structure
HashKey: Place Name
Attribute: Place Data Class
now i would like to search with Place Name,
Ex: I have below data
Newyork {lat: x.xxxxx, lng: x.xxxxxx}
Newzealand {lat: x.xxxxx, lng: x.xxxxxx}
IndialaPolis {lat: x.xxxxx, lng: x.xxxxxx}
when i searching with keyword new, it should return Newyork and Newzealand, I searched google for this, i found that we can get all records by the HashKey
When doing a Query, you can only have exact matches on the HashKey.
However, there is also the Scan operation, that you can use along with a FilterExpression. Just note that Query and Scan perform and consume capacity differently. See the differences here.
Here are example parameters you could use on a Scan with begins_with:
{
table_name: tableName,
filter_expression: "begins_with(#country, :search)",
expression_attribute_names: {
"#country" => "country"
},
expression_attribute_values: {
":search" => "new"
}
}
This article is a good place to start.
Thank you #mark-b and #bruno-buccolo, as you said it is not possible to Search hashkey field
So i created Elastic Search Indexes for that table manually and updating each time the original record updates

dynamodb - scan items by value inside array

I'm doing a table scan. This table has an array as one of its fields, the "apps" field (apps is not a key of any kind). I want to select all rows, whose apps array contains a certain value "MyApp". I tried something of that kind, but my syntax is incorrect:
ComparisonOperator = "#apps CONTAINS :v",
ExpressionAttributeNames = {
'#apps': 'apps'
},
ExpressionAttributeValues = {
":v": "MyApp"
}
Thanks.
The documentation about Condition Expressions clearly states that the appropiate syntax is:
contains(#apps, :v)
The correct request would be:
FilterExpression: "contains(#apps, :v)",
ExpressionAttributeNames: { "#apps": "apps" },
ExpressionAttributeValues: { ":v": "MyApp" }