文档菜单
文档首页
/
MongoDB 手册
/ / /

使用桶模式分组数据

本页内容

  • 关于此任务
  • 步骤
  • 按customerId分组数据
  • 为每个桶添加一个标识符和计数
  • 下一步
  • 使用桶模式查询数据
  • 使用桶模式插入数据
  • 结果
  • 了解更多

桶模式将长时间序列数据分离成独立的对象。将大型数据序列分解成更小的组可以提高查询访问模式并简化应用逻辑。当您有与中心实体(如单个用户进行的股票交易)相关的类似对象时,桶非常有用。

您可以使用桶模式进行分页,通过根据应用每页显示的元素来分组数据。此方法使用MongoDB灵活的数据模型,根据应用所需的数据来存储数据。

提示

时间序列集合 自动应用桶模式,适用于涉及桶式时间序列数据的大多数应用。

考虑以下跟踪股票交易的架构。初始架构没有使用桶模式,每个交易存储在一个单独的文档中。

db.trades.insertMany(
[
{
"ticker" : "MDB",
"customerId": 123,
"type" : "buy",
"quantity" : 419,
"date" : ISODate("2023-10-26T15:47:03.434Z")
},
{
"ticker" : "MDB",
"customerId": 123,
"type" : "sell",
"quantity" : 29,
"date" : ISODate("2023-10-30T09:32:57.765Z")
},
{
"ticker" : "GOOG",
"customerId": 456,
"type" : "buy",
"quantity" : 50,
"date" : ISODate("2023-10-31T11:16:02.120Z")
}
]
)

应用一次显示单个客户的股票交易,每页显示10个交易。为了简化应用逻辑,使用桶模式按10个一组对交易进行分组:customerId

1

将模式重组,每个customerId有一个单独的文档

{
"customerId": 123,
"history": [
{
"type": "buy",
"ticker": "MDB",
"qty": 419,
"date": ISODate("2023-10-26T15:47:03.434Z")
},
{
"type": "sell",
"ticker": "MDB",
"qty": 29,
"date": ISODate("2023-10-30T09:32:57.765Z")
}
]
},
{
"customerId": 456,
"history": [
{
"type" : "buy",
"ticker" : "GOOG",
"quantity" : 50,
"date" : ISODate("2023-10-31T11:16:02.120Z")
}
]
}

使用桶模式

  • 具有相同customerId值的文档被压缩成一个单独的文档,其中customerId是一个顶级字段。

  • 该客户的交易被分组到一个嵌套数组字段中,称为history

2
1db.trades.drop()
2
3db.trades.insertMany(
4 [
5 {
6 "_id": "123_1698349623",
7 "customerId": 123,
8 "count": 2,
9 "history": [
10 {
11 "type": "buy",
12 "ticker": "MDB",
13 "qty": 419,
14 "date": ISODate("2023-10-26T15:47:03.434Z")
15 },
16 {
17 "type": "sell",
18 "ticker": "MDB",
19 "qty": 29,
20 "date": ISODate("2023-10-30T09:32:57.765Z")
21 }
22 ]
23 },
24 {
25 "_id": "456_1698765362",
26 "customerId": 456,
27 "count": 1,
28 "history": [
29 {
30 "type" : "buy",
31 "ticker" : "GOOG",
32 "quantity" : 50,
33 "date" : ISODate("2023-10-31T11:16:02.120Z")
34 }
35 ]
36 },
37 ]
38)

_id字段值是customerIdhistory字段中第一条交易的时间戳(自Unix纪元以来)的拼接。

count字段指示该文档的history数组中有多少个元素。count字段用于实现分页逻辑。

在您将架构更新为使用桶模式后,更新您的应用程序逻辑以读取和写入数据。请参阅以下章节

  • 使用桶模式查询数据

  • 使用桶模式插入数据

在更新后的架构中,每个文档包含应用程序中单个页面的数据。您可以使用 _idcount 字段来确定如何返回和更新数据。

要查询适当页面的数据,请使用正则表达式查询返回指定 customerId 的数据,并使用 skip 返回到正确页面的数据。在 _id 上的正则表达式查询使用 默认 _id 索引,这可以执行高效的查询,无需额外的索引。

以下查询返回客户 123 的第一页交易数据

db.trades.find( { "_id": /^123_/ } ).sort( { _id: 1 } ).limit(1)

要返回后续页面的数据,请指定一个比您要显示数据的页面少一的 skip 值。例如,要显示第 10 页的数据,请运行以下查询

db.trades.find( { "_id": /^123_/ } ).sort( { _id: 1 } ).skip(9).limit(1)

注意

前面的查询没有返回结果,因为示例数据只包含第一页的文档。

现在架构使用桶模式,更新您的应用程序逻辑以将新交易插入正确的桶中。使用更新命令将交易插入具有适当 customerId 和桶的桶中。

以下命令插入客户 customerId: 123 的新交易

db.trades.updateOne( { "_id": /^123_/, "count": { $lt: 10 } },
{
"$push": {
"history": {
"type": "buy",
"ticker": "MSFT",
"qty": 42,
"date": ISODate("2023-11-02T11:43:10")
}
},
"$inc": { "count": 1 },
"$setOnInsert": { "_id": "123_1698939791", "customerId": 123 }
},
{ upsert: true }
)

应用程序每页显示10笔交易。更新过滤器搜索具有customerId: 123的文档,其中count小于10,这意味着该桶不包含一整页的数据。

  • 如果存在匹配"_id": /^123_/的文档且其count小于10,更新命令将新交易推入匹配文档的history数组。

  • 如果没有匹配的文档,更新命令将插入一个包含新交易的新文档(因为upserttrue)。新文档的_id字段是customerId和交易自Unix纪元以来的时间(以秒为单位)的串联。

更新命令的逻辑通过确保没有history数组包含超过10个文档来避免无界数组

运行更新操作后,trades集合具有以下文档

[
{
_id: '123_1698349623',
customerId: 123,
count: 3,
history: [
{
type: 'buy',
ticker: 'MDB',
qty: 419,
date: ISODate("2023-10-26T15:47:03.434Z")
},
{
type: 'sell',
ticker: 'MDB',
qty: 29,
date: ISODate("2023-10-30T09:32:57.765Z")
},
{
type: 'buy',
ticker: 'MSFT',
qty: 42,
date: ISODate("2023-11-02T11:43:10.000Z")
}
]
},
{
_id: '456_1698765362',
customerId: 456,
count: 1,
history: [
{
type: 'buy',
ticker: 'GOOG',
quantity: 50,
date: ISODate("2023-10-31T11:16:02.120Z")
}
]
}
]

实现桶模式后,您不需要在应用程序中集成分页逻辑来返回结果。数据的存储方式与应用程序中使用的方式相匹配。

返回

分组数据