首页 > 解决方案 > 您如何有条件地向嵌套的 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")},
                ...
            ]
        },
        ...
    ]
}

完整规格

  1. 读数可以不按时间顺序插入readings
  2. readings必须保持按升序排序when
  3. 绝不能有 2 个项目连续出现readings在相同的value.
  4. 当 a$push违反规则 3 时,新项目必须替换旧项目,如果(且仅当)它有一个更早的when(如果when是较低的值),否则readings应该保持不变。
  5. 事务必须是原子的。

奖励积分

  1. probe_id如果不存在带有的探测子文档,probes{'probe_id':ObjectId(), 'readings':[]}$push.

可能的情况

  1. 空列表 - 添加项目
  2. 推回,与前一个*成员不同的值 - 添加项目
  3. push back,与前一个成员相同的值 - 什么都不做(不要推动)
  4. 推中间,与前后**成员不同的值 - 添加项目
  5. 推中间,与前一个成员相同的值 - 什么都不做(不要推)
  6. 推中间,与以下成员相同的值 - 替换以下成员
  7. 推前,与以下成员不同的价值 - 添加项目
  8. 推前,与下一个成员相同的值 - 替换下一个成员

*"preceding" = 较早的时间 (lower when)

**"following" = 稍后(更高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 个数据库调用的情况下不存在所需的子文档,它也无法推送所需的子文档。上述规范是否可行?

标签: mongodb

解决方案


解决方案 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"
    ]
  }
])

推荐阅读