mongodb - 您如何有条件地向嵌套的 MongoDB 数组“$push”,以便永远不会有 2 个连续的值相同?
问题描述
数据结构
我从位于不同位置的探针收集读数。读数{'value': <float>, 'when': <timestamp>}
以数组的形式记录,readings
并按时间升序排列。该readings
数组位于由 访问的子文档中probe_id
。
{'location_id': ObjectId('6118d887066a0b17c9a4a531'),
'probes': [
{
'probe_id': ObjectId('6118d887066a0b17c9a4a530'),
'readings': [
{'value': 42, 'when': Date("2021-08-12T05:25:28.905Z")},
{'value': 37, 'when': Date("2021-08-12T08:34:30.405Z")},
{'value': 43, 'when': Date("2021-08-12T12:56:45.043Z")},
...
]
},
...
]
}
完整规格
- 读数可以不按时间顺序插入
readings
。 readings
必须保持按升序排序when
。- 绝不能有 2 个项目连续出现
readings
在相同的value
. - 当 a
$push
违反规则 3 时,新项目必须替换旧项目,如果(且仅当)它有一个更早的when
(如果when
是较低的值),否则readings
应该保持不变。 - 事务必须是原子的。
奖励积分
probe_id
如果不存在带有的探测子文档,probes
则{'probe_id':ObjectId(), 'readings':[]}
在$push
.
可能的情况
- 空列表 - 添加项目
- 推回,与前一个*成员不同的值 - 添加项目
- push back,与前一个成员相同的值 - 什么都不做(不要推动)
- 推中间,与前后**成员不同的值 - 添加项目
- 推中间,与前一个成员相同的值 - 什么都不做(不要推)
- 推中间,与以下成员相同的值 - 替换以下成员
- 推前,与以下成员不同的价值 - 添加项目
- 推前,与下一个成员相同的值 - 替换下一个成员
*"preceding" = 较早的时间 (lower when
)
**"following" = 稍后(更高when
)
笔记
- 相同的项目
when
可以被忽略或替换或被推送(value
在所有相同when
的项目之间提供不同) - 从优化的角度来看,我们通常会向后推(插入更新的、价值更高的、
when
当前代码
使用 PyMongo:
result = collection.update_one(
{'_id': location_id },
{'$push': {'probes.$[probe].readings': {'$each': [new_reading], '$sort': {'when': 1}}}},
array_filters = [{'probe.id': probe_id}],
)
if result.modified_count == 0:
collection.update_one(
{'_id': location_id },
{'$push': {'probes': {'id': probe_id, 'readings': [new_reading]}}},
)
显然,这段代码没有断言连续的项目具有不同的值。如果在没有 2 个数据库调用的情况下不存在所需的子文档,它也无法推送所需的子文档。上述规范是否可行?
解决方案
解决方案 1
插入Push中间的在线示例{"value" : 43 ,"when" : 11}
,与以下成员相同的值 -
在此处替换以下成员测试代码
所有可能的情况(它的 9 个情况,7 + 2 加空探针(“奖金”情况)或只有空读数)
---------------------Empty probes(case 1(the extra case) empty probes)-------------------------------
Before add
{"_id": {"$oid": "61345b2aefdf45b6128444f2"}, "location_id": 1, "probes": []}
Case : Only 1 case exists Member : {value 41, when 3}
{"_id": {"$oid": "61345b2aefdf45b6128444f2"}, "location_id": 1, "probes": [{"probe_id": 4, "readings": [{"value": 41, "when": 3}]}]}
---------------------Empty readings(case 1 empty readings)-------------------------------
Before add
{"_id": {"$oid": "61345b68efdf45b612844650"}, "location_id": 1, "probes": [{"probe_id": 2, "readings": []}]}
Case : Only 1 case exists Member : {value 41, when 3}
{"_id": {"$oid": "61345b68efdf45b612844650"}, "location_id": 1, "probes": [{"probe_id": 2, "readings": [{"value": 41, "when": 3}]}]}
---------------------Not empty readings-------------------------------
Before add
{"_id": {"$oid": "61346a33efdf45b6128496b7"}, "location_id": 1, "probes": [{"probe_id": 2, "readings": [{"value": 42, "when": 5}, {"value": 37, "when": 10}, {"value": 43, "when": 15}, {"value": 41, "when": 20}]}]}
Case : NoConflict start[push front, different value from following member - Add item] Member : {value 41, when 3}
{"_id": {"$oid": "61346a33efdf45b6128496b7"}, "location_id": 1, "probes": [{"probe_id": 2, "readings": [{"value": 41, "when": 3}, {"value": 42, "when": 5}, {"value": 37, "when": 10}, {"value": 43, "when": 15}, {"value": 41, "when": 20}]}]}
Case : Conflict start[push front, same value as following member - Replace following member] Member : {value 42, when 3}
{"_id": {"$oid": "61346a33efdf45b6128496b7"}, "location_id": 1, "probes": [{"probe_id": 2, "readings": [{"value": 42, "when": 3}, {"value": 37, "when": 10}, {"value": 43, "when": 15}, {"value": 41, "when": 20}]}]}
Case : NoConflict middle[push middle, different value from preceding and following** member - Add item] Member : {value 42, when 11}
{"_id": {"$oid": "61346a33efdf45b6128496b7"}, "location_id": 1, "probes": [{"probe_id": 2, "readings": [{"value": 42, "when": 5}, {"value": 37, "when": 10}, {"value": 42, "when": 11}, {"value": 43, "when": 15}, {"value": 41, "when": 20}]}]}
Case : Conflict middle[push middle, same value as preceding member - Do nothing (don't push)] Member : {value 37, when 11}
{"_id": {"$oid": "61346a33efdf45b6128496b7"}, "location_id": 1, "probes": [{"probe_id": 2, "readings": [{"value": 42, "when": 5}, {"value": 37, "when": 10}, {"value": 43, "when": 15}, {"value": 41, "when": 20}]}]}
Case : Conflict middle[push middle, same value as following member - Replace following member] Member : {value 43, when 11}
{"_id": {"$oid": "61346a33efdf45b6128496b7"}, "location_id": 1, "probes": [{"probe_id": 2, "readings": [{"value": 42, "when": 5}, {"value": 37, "when": 10}, {"value": 43, "when": 11}, {"value": 41, "when": 20}]}]}
Case : NoConflict end[push back, different value from preceding* member - Add item] Member : {value 47, when 21}
{"_id": {"$oid": "61346a33efdf45b6128496b7"}, "location_id": 1, "probes": [{"probe_id": 2, "readings": [{"value": 42, "when": 5}, {"value": 37, "when": 10}, {"value": 43, "when": 15}, {"value": 41, "when": 20}, {"value": 47, "when": 21}]}]}
Case : Conflict end[push back, same value as preceding member - Do nothing (don't push)] Member : {value 41, when 21}
{"_id": {"$oid": "61346a33efdf45b6128496b7"}, "location_id": 1, "probes": [{"probe_id": 2, "readings": [{"value": 42, "when": 5}, {"value": 37, "when": 10}, {"value": 43, "when": 15}, {"value": 41, "when": 20}]}]}
询问
它很大但速度很快,它的原子性,全部在 1 个查询中完成,它不对数组进行排序,它插入到正确的位置以保持排序
将此代码移动到您的代码中,将 4 替换为 new ObjectID()(添加方法调用) 将 1 替换为位置 ObjectID 将 2 替换为 prob_id ObjectID 我使用数字而不是日期以便于测试,但无需更改查询
首先
$set
是定义新的 ObjectID(将 4 替换为方法调用)以防探针为空 创建具有空读数的探针$map
进入数组$reduce
为了找到我们将添加新成员的位置,该when
字段定义了位置,查询通过在正确的位置添加来保持数组排序
*我们可以使用 reduce 使数组不仅找到位置,而且如果数组有 >500由于 concat 的成员,它会很慢而无法使用。当我们有位置时,我们检查 4 个基本情况
- 添加空读数(1个案例)
- 在开始时添加(2 例)
- 中间加(3例)
- 在末尾添加(2 例)
- 如果有冲突,他们每个人都有其他
value
情况
db.collection.update({
"location_id": 1
},
[
{
"$set": {
"prob-id": {
"$cond": [
{
"$eq": [
"$probes",
[]
]
},
4,
2
]
}
}
},
{
"$set": {
"probes": {
"$cond": [
{
"$eq": [
"$probes",
[]
]
},
[
{
"probe_id": "$prob-id",
"readings": []
}
],
"$probes"
]
}
}
},
{
"$set": {
"probes": {
"$map": {
"input": "$probes",
"as": "m1",
"in": {
"$cond": [
{
"$ne": [
"$$m1.probe_id",
"$prob-id"
]
},
"$$m1",
{
"$mergeObjects": [
"$$m1",
{
"readings": {
"$let": {
"vars": {
"size_position": {
"$reduce": {
"input": "$$m1.readings",
"initialValue": [
0,
null,
null
],
"in": {
"$let": {
"vars": {
"index_pos": "$$value",
"m2": "$$this"
},
"in": {
"$let": {
"vars": {
"index": {
"$arrayElemAt": [
"$$index_pos",
0
]
},
"pos": {
"$arrayElemAt": [
"$$index_pos",
1
]
}
},
"in": {
"$cond": [
{
"$and": [
{
"$eq": [
"$$pos",
null
]
},
{
"$gt": [
"$$m2.when",
11
]
}
]
},
[
{
"$add": [
"$$index",
1
]
},
"$$index"
],
[
{
"$add": [
"$$index",
1
]
},
"$$pos"
]
]
}
}
}
}
}
}
}
},
"in": {
"$let": {
"vars": {
"asize": {
"$arrayElemAt": [
"$$size_position",
0
]
},
"position": {
"$arrayElemAt": [
"$$size_position",
1
]
}
},
"in": {
"$switch": {
"branches": [
{
"case": {
"$eq": [
"$$asize",
0
]
},
"then": [
{
"value": 43,
"when": 11
}
]
},
{
"case": {
"$eq": [
"$$position",
null
]
},
"then": {
"$let": {
"vars": {
"prv_member": {
"$arrayElemAt": [
"$$m1.readings",
{
"$subtract": [
"$$asize",
1
]
}
]
}
},
"in": {
"$cond": [
{
"$eq": [
"$$prv_member.value",
43
]
},
"$$m1.readings",
{
"$concatArrays": [
"$$m1.readings",
[
{
"value": 43,
"when": 11
}
]
]
}
]
}
}
}
},
{
"case": {
"$eq": [
"$$position",
0
]
},
"then": {
"$let": {
"vars": {
"next_member": {
"$arrayElemAt": [
"$$m1.readings",
0
]
}
},
"in": {
"$cond": [
{
"$eq": [
"$$next_member.value",
43
]
},
{
"$cond": [
{
"$lt": [
11,
"$$next_member.when"
]
},
{
"$concatArrays": [
[
{
"value": 43,
"when": 11
}
],
{
"$slice": [
"$$m1.readings",
1,
"$$asize"
]
}
]
},
"$$m1.readings"
]
},
{
"$concatArrays": [
[
{
"value": 43,
"when": 11
}
],
"$$m1.readings"
]
}
]
}
}
}
}
],
"default": {
"$let": {
"vars": {
"next_member": {
"$arrayElemAt": [
"$$m1.readings",
"$$position"
]
},
"prv_member": {
"$arrayElemAt": [
"$$m1.readings",
{
"$subtract": [
"$$position",
1
]
}
]
}
},
"in": {
"$switch": {
"branches": [
{
"case": {
"$and": [
{
"$ne": [
"$$next_member.value",
43
]
},
{
"$ne": [
"$$prv_member.value",
43
]
}
]
},
"then": {
"$concatArrays": [
{
"$slice": [
"$$m1.readings",
0,
"$$position"
]
},
[
{
"value": 43,
"when": 11
}
],
{
"$slice": [
"$$m1.readings",
"$$position",
{
"$add": [
"$$asize",
1
]
}
]
}
]
}
},
{
"case": {
"$eq": [
"$$prv_member.value",
43
]
},
"then": "$$m1.readings"
}
],
"default": {
"$concatArrays": [
{
"$slice": [
"$$m1.readings",
0,
"$$position"
]
},
[
{
"value": 43,
"when": 11
}
],
{
"$slice": [
"$$m1.readings",
{
"$add": [
"$$position",
1
]
},
{
"$add": [
"$$asize",
1
]
}
]
}
]
}
}
}
}
}
}
}
}
}
}
}
}
]
}
]
}
}
}
}
},
{
"$unset": [
"prob-id"
]
}
])
推荐阅读
- flutter - Flutter - 上下文仅传递给 BottomNavigationBarItem 之一
- python - 如何重用keras功能模型的层级
- selenium - Selenium 循环,表格缺少第一行和最后一列
- c# - 如何在 .NET 的控制器中支持作为 formbody 的继承?
- java - 在 NetBens 中使用 addDateEvaluator() 方法后如何启用 JCalendar 按钮?
- flutter - Flutter:我们在哪里可以使用线性渐变而不是容器
- flutter - 使用 Riverpod 时可以保存全局 ref 对象吗
- javascript - 反应路由器 v6 在组件之外导航
- python - Python:局部函数中的全局变量可能吗?
- java - 在 Spring Boot jpa 中存储日期的最佳方法是什么?