DynamoDB 如何查询每个用户的一个 public 最新项目?

DynamoDB how to query by one public latest item by each user?

这是我的 DynamoDB 数据结构。

---------------------------------------------------------------------
id  |  author  |  status  |  content  |  createdAt
---------------------------------------------------------------------
id1 |  user1   |  PRIVATE |   pcon    |  2019-09-09T17:54:09.843Z
id1 |  user1   |  PUBLIC  |   hello   |  2019-09-08T17:54:09.843Z
id2 |  user2   |  PUBLIC  |   world   |  2019-09-07T17:54:09.843Z
id1 |  user1   |  PUBLIC  |   hello1  |  2019-09-07T17:54:09.843Z
---------------------------------------------------------------------

如何使用 DynamoDB 从每个用户查询一个最新的 PUBLIC 内容?

预期查询结果:

items[
    {
        id: id1,
        author: user1,
        status: PUBLIC,
        content: hello,
        createdAt: 2019-09-08T17:54:09.843Z
    },
        {
        id: id2,
        author: user2,
        status: PUBLIC,
        content: world,
        createdAt: 2019-09-07T17:54:09.843Z
    },
]

我能够使用以下代码获取所有 PUBLIC 项,但找不到从中获取最新项的方法。(放大自定义解析器映射模板)


{
  "version": "2017-02-28",
  "operation": "Query",
  "query": {
    "expression": "#privacy = :privacy",
    "expressionNames": {
        "#privacy": "privacy"
    },
    "expressionValues": {
        ":privacy": {
            "S": "PUBLIC"
        }
    }
  },
  "scanIndexForward": #if( $context.args.sortDirection == "ASC" ) true #else false #end,
  "limit": $limit,
  "nextToken": #if( $context.args.nextToken ) "$context.args.nextToken" #else null #end,
  "index": "privacy"
} 

你需要在上面引入第二个table和一个GSI(Global secondary index),结构如下:

  • userId <-- table 的分区键
  • category <-- GSI 的分区键
  • createdAt <-- GSI 的排序键
  • id

userId 属性是一个唯一标识用户的值(IIUC 它实际上可能是 post 中描述的 table 中的 author 字段)

category 属性最初可能看起来有点奇怪:它包含几个硬编码值之一。目前我只能想到一个这样的值:"public_content_page"。尽管如此,即使未来没有新的类别出现,也需要这个属性作为GSI的分区键(所以我们无法避免)。

createdAtid 属性与 post 中描述的 table 中的属性相同。

要按您希望的顺序获取项目,您需要按如下方式查询 GSI:

{ 
  "TableName": <your_table_name>,
  "IndexName": <your_GSI_name>
  "KeyConditionExpression": "category = :v1",
  "ExpressionAttributeValues": {":v1": {"S": "public_content_page"}}
   "ScanIndexForward": false,
}

因为 table 的主键是 userId 这个 table 将只为每个用户保存一个项目。

因为 table 中的所有项都具有相同的 category 值并且 GSI 的分区键是 category 属性,所以查询 GSI 就是查询其中的整个项集table.

因为 createdAt 属性是 GSI 的排序键,所以此查询返回的结果将按时间顺序排序。

当然,您需要填充这个table。基本上,每次你 put()/update()/delete() 第一个 table 中的一个项目(你的 post 中描述的那个)你需要做第二个 table 中的 update() (我在回答中介绍的那个)。在那次更新中,您需要使用 ConditionExpression 来确保仅当新的 createdAt 值大于该项目的 createdAt 值时才覆盖该项目。

您需要记住,第二个 table 的 update() 有可能不会执行(因为您的进程将在更新第一个 [=86= 后终止) ] 并且就在更新第二个之前)。您可以执行计划扫描,以某个定期计划从第一个 table 重建第二个 table,或者您可以 triggers).

其他想法

此处显示的第 GSI 仅包含第一个 table 项的 id。因此,为了获得实际项目的内容,您需要获取查询结果并使用查询返回的 id 值在第一个 table 上执行多个 get()。您可以使用 BatchGetItem 在一个请求中执行多个 get() 操作。或者,您可以使用第二个 table 的不同结构:它不会保存第一个 table 中某个项目的 id,而是保存 content 值。这将使您摆脱额外的 get()。另一方面,它会使财务成本更高(第二个 table 现在将存储更多数据)并且会使对第二个 table 的更新更频繁(因为 content 字段是可能是一个经常编辑的字段,每次这样的编辑都会更新到第二个 table)。

最后,您可以使用 TransactWriteItems 更新两个 tables 在单个事务中。不过,您仍然需要在 createdAt 属性上使用条件。