【问题标题】:Query for products in a certain category查询某类产品
【发布时间】:2015-04-24 15:13:36
【问题描述】:

我在 DynamoDb 中有一个简单的“products”表。每个产品都有一个categories 属性,它是一组类别 ID,如下所示:

[{ "N" : "4" },{ "N" : "5" },{ "N" : "6" },{ "N" : "8" }]

products 表有id(哈希键)和accountId(范围键)

是否可以进行查询以查找属于类别 6 和 accountId 1 的所有产品无需进行扫描?或者我可以通过其他方式对此进行建模吗?

如果它是一个关系数据库,我将有一个产品到类别表,并加入产品。如果我在 Dynamo 中有一个类似的表,那么我需要为产品表中的每个产品创建一个 GetItem,这感觉是个坏主意?

【问题讨论】:

    标签: amazon-dynamodb


    【解决方案1】:

    根据您的描述,听起来最好的解决方法是使用GSI

    您的表格结构如下:

    • 哈希键:id
    • 范围键:accountId
    • 属性-categories

    您将创建一个具有此结构的全局二级索引:

    • 哈希键:accountId
    • 范围键:id
    • 属性-categories

    然后您就可以使用您提到的条件查询该索引:

    1. accountId = 1
    2. categories contains 6

    这是我针对 DynamoDB local 编写的一个快速示例,它将所有属性投影到索引上。

    import com.amazonaws.auth.BasicAWSCredentials;
    import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
    import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
    import com.amazonaws.services.dynamodbv2.document.DynamoDB;
    import com.amazonaws.services.dynamodbv2.document.Index;
    import com.amazonaws.services.dynamodbv2.document.Item;
    import com.amazonaws.services.dynamodbv2.document.QueryFilter;
    import com.amazonaws.services.dynamodbv2.document.Table;
    import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;
    import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
    import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
    import com.amazonaws.services.dynamodbv2.model.GlobalSecondaryIndex;
    import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
    import com.amazonaws.services.dynamodbv2.model.KeyType;
    import com.amazonaws.services.dynamodbv2.model.Projection;
    import com.amazonaws.services.dynamodbv2.model.ProjectionType;
    import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
    import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
    import com.amazonaws.services.dynamodbv2.util.Tables;
    public class StackOverflow {
    
        private static final String EXAMPLE_TABLE_NAME = "example_table";
        private static final String HASH_KEY = "id";
        private static final String RANGE_KEY = "accountId";
        private static final String GSI = "accountIdToId";
        private static final String CATEGORIES = "categories";
    
        public static void main(String[] args) throws InterruptedException {
            AmazonDynamoDB
                client =
                new AmazonDynamoDBClient(new BasicAWSCredentials("accessKey", "secretKey"));
            client.setEndpoint("http://localhost:4000");
            DynamoDB dynamoDB = new DynamoDB(client);
            if (Tables.doesTableExist(client, EXAMPLE_TABLE_NAME)) {
                client.deleteTable(EXAMPLE_TABLE_NAME);
            }
    
            CreateTableRequest createTableRequest = new CreateTableRequest();
            createTableRequest.withTableName(EXAMPLE_TABLE_NAME);
            createTableRequest.withKeySchema(new KeySchemaElement(HASH_KEY, KeyType.HASH),
                                             new KeySchemaElement(RANGE_KEY, KeyType.RANGE));
            createTableRequest.withAttributeDefinitions(
                new AttributeDefinition(HASH_KEY, ScalarAttributeType.S),
                new AttributeDefinition(RANGE_KEY, ScalarAttributeType.S));
            createTableRequest.withProvisionedThroughput(new ProvisionedThroughput(15l, 15l));
            // GSI definition
            final GlobalSecondaryIndex
                accountIdToId =
                new GlobalSecondaryIndex().withIndexName(GSI).withKeySchema(
                    new KeySchemaElement(RANGE_KEY, KeyType.HASH),
                    new KeySchemaElement(HASH_KEY, KeyType.RANGE)).withProvisionedThroughput(
                    new ProvisionedThroughput(10l, 10l)).withProjection(
                    new Projection().withProjectionType(ProjectionType.ALL));
            createTableRequest.withGlobalSecondaryIndexes(accountIdToId);
    
            final Table table = dynamoDB.createTable(createTableRequest);
            table.waitForActive();
    
            table.putItem(new Item()
                              .withPrimaryKey(HASH_KEY, "1", RANGE_KEY, "6")
                              .withNumberSet(CATEGORIES, 1, 2, 5, 6));
    
            table.putItem(new Item()
                                     .withPrimaryKey(HASH_KEY, "2", RANGE_KEY, "6")
                                     .withNumberSet(CATEGORIES, 5, 6));
    
            table.putItem(new Item()
                                     .withPrimaryKey(HASH_KEY, "5", RANGE_KEY, "6")
                                     .withNumberSet(CATEGORIES, 1, 2));
    
            table.putItem(new Item()
                                     .withPrimaryKey(HASH_KEY, "5", RANGE_KEY, "8")
                                     .withNumberSet(CATEGORIES, 1, 2, 6));
    
            System.out.println("Scan the table, no filters");
            table.scan().forEach(System.out::println);
            System.out.println();
    
            final Index gsi = table.getIndex(GSI);
    
            System.out.println("Scan the GSI without filter");
            gsi.scan().forEach(System.out::println);
            System.out.println();
            System.out.println("Query the GSI with range key condition and contains");
    
            final QuerySpec querySpec = new QuerySpec()
                .withHashKey(RANGE_KEY, "6")
                .withQueryFilters(new QueryFilter(CATEGORIES).contains(6));
            gsi.query(querySpec).forEach(System.out::println);
            System.out.println();
        }
    }
    

    输出:

    Scan the table, no filters
    { Item: {accountId=6, id=1, categories=[1, 2, 5, 6]} }
    { Item: {accountId=6, id=5, categories=[1, 2]} }
    { Item: {accountId=8, id=5, categories=[1, 2, 6]} }
    { Item: {accountId=6, id=2, categories=[5, 6]} }
    
    Scan the GSI without filter
    { Item: {accountId=6, id=1, categories=[1, 2, 5, 6]} }
    { Item: {accountId=6, id=5, categories=[1, 2]} }
    { Item: {accountId=8, id=5, categories=[1, 2, 6]} }
    { Item: {accountId=6, id=2, categories=[5, 6]} }
    
    Query the GSI with range key condition and contains
    { Item: {accountId=6, id=1, categories=[1, 2, 5, 6]} }
    { Item: {accountId=6, id=2, categories=[5, 6]} }
    

    【讨论】:

    • 我对 Dynamo 很陌生,所以如果我错了,请纠正我,但据我了解,incontains 仅支持 scan i> 而不是 查询?
    • @Christoffer 我更新了我的答案以更清楚,因为contains 是你想要的。您应该能够在 scanquery* 中同时使用 incontains
    • 感谢您的示例!我正在使用 PHP SDK,所以我不完全理解你在做什么。我创建了一个 GSI; accountID 作为哈希键,id 作为键。然后我试着像你一样查询:{"TableName":"products","KeyConditions":{"accountId":{"ComparisonOperator":"EQ","AttributeValueList":[{"N":"1"}]},"categories":{"ComparisonOperator":"CONTAINS","AttributeValueList":[{"N":"6"}]}},"IndexName":"accountId-id-index"}。但我收到了这个错误:Query condition missed key schema element: id
    • 在评论中很难判断categories 是否是KeyConditions 的成员。它应该是QueryFilter 参数的一部分。此外,请确保您的 GSI 将 accountId 作为哈希键(从错误消息中看起来并不像)。
    【解决方案2】:

    创建另一个表,并在更新主表时对其进行更新。真的,无论如何,这就是在 RDBMS 中发生的事情,只是它在后台。当亚马逊为表设置二级索引时,它们基本上只是自动化了人们一直在做的事情。

    【讨论】:

    • 如果可能的话,我想避免这种情况。如果将产品分为 5 个类别,我将不得不将产品数据保存在 5 个副本中,这感觉很冒险 :)
    • 不幸的是,当您在 Dynamo 中的结构中有数据时,您无法对其进行索引。这是一个简单的野兽。这是我喜欢 Mongo 的一件事,你可以在结构中建立索引。
    猜你喜欢
    • 2021-02-21
    • 1970-01-01
    • 2015-09-05
    • 1970-01-01
    • 1970-01-01
    • 2019-07-09
    • 1970-01-01
    • 2011-03-16
    • 1970-01-01
    相关资源
    最近更新 更多