在亚马逊DynamoDB中查询嵌套属性

18
如何在Amazon DynamoDB中高效查询嵌套属性?文档结构如下,允许将相关信息存储在文档本身中(而不是引用它)。将研讨会嵌套在课程中存储是有意义的,因为它们很可能会与课程一起查询(它们都是特定于课程的,即课程有许多研讨会,而研讨会属于一个课程)。在我从中迁移的CouchDB中,我可以编写一个视图来查询一些嵌套属性。我了解到我不能投影任何不是顶级属性的东西到dynamodb次要索引中,因此这种方法似乎行不通。这让我回到了问题:如果我不能将它们用作索引中的键,那么如何在没有扫描的情况下高效地查询嵌套属性?例如,如果我想获取Nelson Mandela Theatre的平均出席率,如何查询所有具有“Nelson Mandela Theatre”位置的研讨会中registrations和attendees值,而不必进行扫描?
{
    “course_id”: “ABC-1234567”,
    “course_name”: “Statistics 101”,
    “tutors”: [“Cognito-sub-1”, “Cognito-sub-2”],
    “seminars”: [ 
        {
            “seminar_id”: “XXXYYY-12345”,
            “epoch_time”: “123456789”,
            “duration”: “5400”,
            “location”: “Nelson Mandela Theatre”,
            “name”: “How to lie with statistics”,
            “registrations”: “92”,                
            “attendees”: “61”
        },
        {
            “seminar_id”: “BBBCCC-44444”,
            “epoch_time”: “155555555”,
            “duration”: “5400”,
            “location”: “Nelson Mandela Theatre”,
            “name”: “Statistical significance for dog owners”,
            “registrations”: “244”,
            “attendees”: “240”
        },
        {
            “seminar_id”: “XXXAAA-54321”,
            “epoch_time”: “223456789”,
            “duration”: “4000”,
            “location”: “Starbucks”,
            “name”: “Is feral cat population growth a leading indicator for the S&P 500?”,
            “registrations”: “40”                
        }
    ]
}

{
    “course_id”: “CJX-5553389”,
    “course_name”: “Cat Health 101”,
    “tutors”: [“Cognito-sub-4”, “Cognito-sub-9”],
    “seminars”: [ 
        {
            “seminar_id”: “TTRHJK-43278”,
            “epoch_time”: “123456789”,
            “duration”: “5400”,
            “location”: “Catwoman Hall”,
            “name”: “Emotional support octopi for cats”,
            “registrations”: “88”, 
            “attendees”: “87”
        },
        {
            “seminar_id”: “BBBCCC-44444”,
            “epoch_time”: “123666789”,
            “duration”: “5400”,
            “location”: “Nelson Mandela Theatre”,
            “name”: “Statistical significance for cat owners”,
            “registrations”: “44”,
            “attendees”: “44”
        }
    ]
}
5个回答

14

无法为嵌套属性(即Dynamodb中的文档数据类型)创建索引。

文档类型 - 文档类型可以表示具有嵌套属性的复杂结构,例如您在JSON文档中找到的结构。文档类型是列表和映射。

查询API:

查询操作仅搜索主键属性值,并支持一部分比较运算符以细化搜索过程中的键属性值。

扫描API:

扫描操作扫描整个表。您可以指定要应用于结果的过滤器,以便在完整扫描后将返回给您的值。

为了使用查询API,需要哈希键值。OP没有任何信息表明哈希键值可用。根据OP的说法,需要通过Dynamodb List数据类型中的location属性查询数据。现在,选择查看GSI。

请详细阅读有关GSI的内容。其中一条规则是只能使用顶级属性创建GSI。因此,位置无法用于创建索引。

因此,已经排除了创建GSI以使用查询API的可能性。

索引键属性可以包含基本表中的任何顶级字符串、数字或二进制属性;其他标量类型、文档类型和集合类型不允许。

因为以上提到的原因,如果哈希键值不可用,无法使用查询API基于“位置”属性获取数据。
如果哈希键值可用,则可以使用FilterExpression过滤数据。过滤复杂列表数据类型中的数据的唯一方法是使用CONTAINS函数。为了使用CONTAINS函数,需要匹配发生中的所有属性(即seminar_id、location、duration和所有其他属性)的数据。因此,使用当前数据模型肯定无法实现OP中提到的用例。
建议的替代解决方案:
重新设计如下所述的数据结构可能是解决问题的选项。使用查询API绝对没有其他可用的解决方案来实现用例。
主表:
Course Id-哈希键
seminar_id-排序键
GSI:
研讨会地点-哈希键
Course Id-排序键
在DynamoDB表中,每个键值必须是唯一的。但是全局二级索引中的键值不需要是唯一的。
现在,您可以使用GSI上的查询API获取“研讨会位置”等于“Nelson Mandela Theatre”的数据。如果您知道值,可以在查询API中使用课程ID。查询API可能会在结果集中提供多个项目。如果您想根据一些非键属性进一步过滤数据,可以使用FilterExpression。

问题询问如何使用嵌套属性作为哈希键创建索引。这个答案说解决方案是创建一个具有嵌套属性作为哈希键的GSI。如何实现呢? - Harry
索引只能为顶级属性创建。 - notionquest
是的,这个问题的目的就在于——如何在不使用扫描的情况下查询嵌套属性?无法对嵌套属性进行查询似乎违背了嵌套结构的目的。 - Harry
8
很遗憾,你没有理解这个概念。你可能需要阅读更多关于Dynamodb的内容。 - notionquest

2
这是一个来自这里的示例,它使用了过滤表达式,并且是使用扫描操作实现的,但也许你可以将类似的方法应用于查询而不是扫描(请查看API)。
{
    "TableName": "MyTable",
    "FilterExpression": "#k_Compatible.#k_RAM = :v_Compatible_RAM",
    "ExpressionAttributeNames": {
        "#k_Compatible": "Compatible",
        "#k_RAM": "RAM"
    },
    "ExpressionAttributeValues": {
        ":v_Compatible_RAM": "RAM1"
    }
}

扫描操作成本非常高。 - Lenzman

0

我还没有使用DynamoDB的经验,但因为计划在我的下一个项目中使用它,所以已经开始学习。

根据AWS文档,我了解到你的问题的答案是:无法高效地查询嵌套属性。

通过查看最佳实践,特别是DynamoDB中使用二级索引的最佳实践,可以理解正确的方法应该是在相同的Partition Key下使用不同的行类型,如此处所示。然后,在相同的course_id下,您将拥有一个通用的排序键(sk)。第一条记录将具有sk =“Details”,其中包含课程数据,然后是其他记录,例如“seminar-1”及其数据等。 然后,您将设置要查询的研讨会属性作为SGI(Secondary Global Index),请记住每个表只能有5个SGI。

希望能有所帮助。


0

您可以通过一种方法让其在扫描中正常工作: 将对象存储为stringify格式,如下所示: { "language": "[{\"language\":\"Male\",\"proficiency\":\"Female\"}]" } 然后可以执行扫描操作 language: { contains: "Male" }

在客户端上,您可以执行JSON.parse(language)


0

您可以使用文档路径来过滤值。请使用seminars.location作为文档路径。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接