MongoDB:条件更新将数组视为无序

2wnc66cl  于 2023-01-16  发布在  Go
关注(0)|答案(2)|浏览(108)

我需要集合中的每个文档仅在其内容不同时才更新,而不管嵌套列表中元素的顺序如何。
从根本上说,如果元素完全相同,而不管它们的顺序如何,那么两个版本应该是相同的。默认情况下,MongoDB不这样做。

def upsert(query, update):
    # collection is a pymongo.collection.Collection object
    result = collection.update_one(query, update, upsert=True)
    print("\tFound match: ", result.matched_count > 0)
    print("\tCreated: ", result.upserted_id is not None)
    print("\tModified existing: ", result.modified_count > 0)

query = {"name": "Some name"}

update = {"$set": {
    "products": [
        {"product_name": "a"},
        {"product_name": "b"},
        {"product_name": "c"}]
}}
print("First update")
upsert(query, update)

print("Same update")
upsert(query, update)

update = {"$set": {
    "products": [
        {"product_name": "c"},
        {"product_name": "b"},
        {"product_name": "a"}]
}}
print("Update with different order of products")
upsert(query, update)

输出:

First update
Found match:  False
Created:  True
Modified existing:  False

Same update 
Found match:  True
Created:  False
Modified existing:  False

Update with different order of products
Found match:  True
Created:  False
Modified existing:  True

最后一次更新确实修改了文档,因为产品的顺序确实不同。
我确实找到了一个可行的解决方案,那就是比较查询文档内容的排序和新文档内容的排序。
感谢Zero Piraeusresponse提供了一种简短方便的排序比较方法。

def ordered(obj):
    if isinstance(obj, dict):
        return sorted((k, ordered(v)) for k, v in obj.items())
    if isinstance(obj, list):
        return sorted(ordered(x) for x in obj)
    else:
        return obj

我应用它来比较文档的当前版本和新版本。如果它们的排序不同,我就应用更新。

new_update = {
    "products": [
        {"product_name": "b"},
        {"product_name": "c"},
        {"product_name": "a"}]
}

returned_doc = collection.find_one(query)
# Merging remote document with local dictionary
merged_doc = {**returned_doc, **new_update}
if ordered(returned_doc) != ordered(merged_doc):
    upsert(query, {"$set": new_update})
    print("Updated")
else:
    print("Not Updated")

输出:

Not Updated

这是可行的,但是这依赖于python来做比较,在读和写之间引入了延迟。
有没有一种方法可以原子地完成它?或者,更好的是,有没有一种方法可以将MongoDB集合设置为采用某种"数组内的顺序无关紧要"的模式?
这是泛型实现的一部分。文档在其结构中可以有任何类型的嵌套。

q8l4jmvw

q8l4jmvw1#

  • @nimrodserok* 正确地指出了我第一个答案中的一个缺陷,下面是我更新的答案,它是his answer的一个微小的变化。

这也应该保留upsert选项。

new_new_update = [
  {
    "$set": {
      "products": {
        "$let": {
          "vars": {
            "new_products": [
              {"product_name": "b"},
              {"product_name": "c"},
              {"product_name": "a"}
            ],
            "current_products": {
              # need this for upsert
              "$ifNull": ["$products", []]
            }
          },
          "in": {
            "$cond": [
              {"$setEquals": ["$$current_products", "$$new_products"]},
              "$$current_products",
              "$$new_products"
            ]
          }
        }
      }
    }
  }
]

这里有一个mongoplayground.net example来演示这个概念,您可以更改"name"的值来验证upsert选项。
我很好奇这个update_oneresult值是多少。

wlwcrazw

wlwcrazw2#

EDIT:根据您的评论(以及与other question的相似之处),我建议:

db.collection.updateMany(
  {"name": "Some name"},
  [{
    $set: {products: {
        $cond: [
          {$setEquals: [["a", "c", "b"], "$products.product_name"]},
          "$products",
          [{"product_name": "a"}, {"product_name": "c"}, {"product_name": "b"}]
        ]
    }}
  }]
)

了解它在playground example上的工作原理

原答复

一个选项是使用更新的查询部分仅处理与条件匹配的文档:

db.collection.update(
  {"name": "Some name",
   "products.product_name": {
    $not: {$all: ["a", "b", "c"]}}
  },
  {"$set": {
    "products": [
      {"product_name": "b"},
      {"product_name": "c"},
      {"product_name": "a"}
    ]
  }}
)

了解它在playground example上的工作原理

相关问题