创建一个视图,从集合中获取每个最新文档



我有一个每天从HRIS中提取状态数据的集合。我们可以将状态数据用于各种有趣的事情,比如预测分析。

然而,状态变化数据无止境地增长(目前,这是我们需要解决的另一项技术债务)。但是,我想首先创建一个视图,该视图只获取集合中的最新记录。所以我有以下视图创建代码:

db.rawEmployeeStatus.aggregate().match({dateOfStateCapture:   
db.rawEmployeeStatus.find({})
.sort({$natural : -1})
.limit(1)
.toArray()[0]
.dateOfStateCapture})      
.saveAsView("employeeStatusCurrentState",{dropIfExists:false})

然而,此视图的问题是,它在创建视图时获得最近的记录,然后将该数据用于视图。理想情况下,视图中的数据应始终由集合中的最新记录填充。

如果dateOfStateCapture字段标识集合中最新的文档,那么这样的视图可能会满足您的需求:

db.createView(
"employeeStatusCurrentState", 
"rawEmployeeStatus", 
[
{ $sort: { dateOfStateCapture: -1 } },
{ $limit: 1 }
], 
);

或者,假设您正在为_id字段使用自动生成的ObjectId(其值会增加),则以下视图将适合您的需要:

db.createView(
"employeeStatusCurrentState", 
"rawEmployeeStatus", 
[
{ $sort: { _id: -1 } },
{ $limit: 1 }
], 
);

以下是后者工作的快速演示:

> db.employeeStatusCurrentState.find()
[ { _id: 3, val: 'c' } ]
>
> db.rawEmployeeStatus.insertOne({ _id: 4, val: 'd' })
{ acknowledged: true, insertedId: 4 }
>
> db.employeeStatusCurrentState.find()
[ { _id: 4, val: 'd' } ]

另一个答案中关于视图是静态的说法实际上并不正确。如该答案中所述,有一个页面演示了如何通过$merge实现视图,但这与它们的只读视图无关。这里有一小部分是关于两者之间的比较。


现在让我们深入了解问题中提到的行为和约束的细节。首先从评论中重新审视这一点:

什么是saveAsView()?

它创建了一个视图。。。

当然,很明显,这是一般的想法。但这里的细节对于回答你的问题非常重要。什么类型的视图(例如,它被物化了吗),它是在哪里创建的?例如,如果实用程序正在保存查询的结果(缓存/具体化它),

saveAsView()而不是MongoDB提供的助手函数。官方帮助程序是createView(),因此我提出了问题。

通过一些额外的研究,我认为saveAsView()是NoSQLBooster for MongoDB(以前的"MongoBooster")实用程序从3.3版本开始提供的功能。我下载并尝试了该实用程序,根据下面的示例,它看起来确实在数据库中创建了一个非物化视图。但这一观点定义的细节似乎正是造成混乱和麻烦的原因。

通过这些步骤,我从数据库中的一个集合开始:

test> show collections
rawEmployeeStatus
test> db.rawEmployeeStatus.find()
[
{ _id: 1, dateOfStateCapture: 1 },
{ _id: 2, dateOfStateCapture: 2 },
{ _id: 3, dateOfStateCapture: 3 }
]

然后,我在NoSQLBooster(版本7.1.16)中运行了这个问题的确切命令。之后,我可以在我的数据库中看到视图:

test> show collections
employeeStatusCurrentState  [view]
rawEmployeeStatus

如果我查询它,我会得到一个预期的文档:

test> db.employeeStatusCurrentState.find()
[ { _id: 3, dateOfStateCapture: 3 } ]
test>

事实上,正如您所提到的,如果我在rawEmployeeStatus中插入一个新文档,然后再次查询视图,我仍然会得到3:的_id的前一个文档

test> db.rawEmployeeStatus.insertOne({_id:4, dateOfStateCapture:4})
{ acknowledged: true, insertedId: 4 }
test> db.employeeStatusCurrentState.find()
[ { _id: 3, dateOfStateCapture: 3 } ]

当我们查看视图定义时,我们可以看到原因:

test> db.getCollectionInfos({name:"employeeStatusCurrentState"})
[
{
name: 'employeeStatusCurrentState',
type: 'view',
options: {
viewOn: 'rawEmployeeStatus',
pipeline: [ { '$match': { dateOfStateCapture: 3 } } ]
},
info: { readOnly: true }
}
]

具体来说,看看定义视图的pipeline

[ { '$match': { dateOfStateCapture: 3 } } ]

这里发生的情况是,在将命令的内部部分发送到数据库以存储在视图中之前,执行该命令以解析为一个值。所以这个:

db.rawEmployeeStatus.find({})
.sort({$natural : -1})
.limit(1)
.toArray()[0]
.dateOfStateCapture

解析为仅3(在我的示例中),并且是存储在视图定义中的内容。这是由NoSQLBooster客户端执行的,因为类似.toArray()[0]的内容不是有效的聚合语法,因此对视图中的存储无效。


总结一下,MongoDB中的经典视图是而不是静态(具体化)。在您的特定情况下,使用的第三方工具以一种不明显的方式创建了视图。这要求它将一些定义解析为静态值,该值是作为视图定义本身的一部分提供的。这个答案的顶部提供了创建一个符合您需求的视图(或者至少为您指明正确的方向)的替代语法。

我可能错了,但我的印象是,在基于源聚合结果创建或刷新(标准视图和按需实体化视图)时,mongo视图总是静态的。

如果您查看按需材料化视图的文档,通常有3个不同的独立步骤

  1. 定义按需物化视图
  2. 执行初始运行
  3. 刷新实体化视图

我相信您想要刷新要更新的材质化视图,当在构建该视图的集合中插入一个插件时。然而,事实并非如此。实体化视图本质上是一个基于聚合的虚拟集合,它不知道源集合数据实体的更改可能对自身产生的影响。如果真的这样就太好了!但您的视图通常是基于优化子集的,因此插入或更新可能不会更改视图(例如,虚拟聚合结果)。

因此,为了在插入后更新视图,您必须始终遵循刷新视图的最后一步(例如,再次运行聚合查询)。显然,在刷新后这样做是相当激进的,当然也不是表演性的。

在维护最新状态的视图的情况下,可能值得进行应用程序级回调,以检查每次插入/更新后是否有理由更新视图。同样,这将取决于您案例中的记录。如果它是一个简单的文档过滤器,因为所有更新都是插入,那么您可能会在每次插入时刷新。如果状态更新在单个记录的数组属性中,则并非所有更新都可能触发刷新等

您可以使用更改流来侦听对集合的更改并相应地更新视图。这里有一个链接:https://docs.mongodb.com/manual/changeStreams/

这是因为上面的视图创建代码在创建视图时只查看最新的记录,而不会在向集合中添加新记录时动态更新视图。

最新更新