首页 > 解决方案 > 为什么 mongodb 中的 addToSet 在数组字段中不起作用?

问题描述

集合中的文档有一个子文档的数组字段,每个字段都有一个计数器,应该增加到三个,但是如果数组没有具有给定键的子文档,它应该使用默认值创建它.

$addToSet的文档说:

行为

$addToSet 仅确保没有重复项添加到集合中,并且不会影响现有的重复元素。$addToSet 不保证修改集中元素的特定顺序。

缺少字段

如果您在要更新的文档中不存在的字段上使用 $addToSet,则 $addToSet 会创建以指定值作为其元素的数组字段。

问题是如果数组字段不存在,则不会在文档中创建它,如文档中所述。

这是我目前用来完成操作的:

// increase counter in element of array if element exist and counter is less than 3
collection.updateOne({
        key_1,
        "array.key_2": key_2,
        "array.counter": {$lt: 3}
    }, {
        $inc: {"array.$.counter": 1}
    })
    .then(res => {
        console.log("!!!!1:", res.modifiedCount, res.upsertedId, res.upsertedCount, res.matchedCount);

        if (res.matchedCount) return res.matchedCount;

        // create element in array with the default values,
        // also create the array field if it doesn't exist
        collection.updateOne({
                key_1
            }, {
                $addToSet: {
                    array: {key_2, counter: 1}
                }
            })
            .then(res => {
                console.log("!!!!2:", res.modifiedCount, res.upsertedId, res.upsertedCount, res.matchedCount);
                return res.matchedCount;
            })
            .catch(e => console.log(e))
    })
    .catch(e => console.log(e))


在第二个查询中使用upsert,如果它不存在则创建数组字段,但是当array.counter在后续调用中达到 3 以增加其值时,该操作会在数组中创建一个新的子文档,其值与array.key_2and相同array.date,有效复制该条目虽然array.counter设置为 1 而不是 3。

我正在使用 mongo 版本 4.2.1

更新:

在尝试对第二个子文档运行操作之前,下面有一个示例文档:

{
  "key_1": 1,
  "array": [
    {
      "key_2": 1,
      "counter" 1
    }, {
      "key_2": 2,
      "counter" 3
    }
  ]
}

这是我在使用时得到的结果upsert

{
  "key_1": 1,
  "array": [
    {
      "key_2": 1,
      "counter" 1
    }, {
      "key_2": 2,
      "counter" 3
    }, {
      "key_2": 2,
      "counter" 1
    }
  ]
}


该操作正在复制数组中的第二个子文档,但如果upsert未使用,则如果它不在父文档中,则不会创建数组字段,这与文档中所说的预期行为相反$addToSet

更新 2

这些是重现问题的步骤:

运行key_1设置为 1 并upsert禁用的操作。没有任何查询修改文档。array未创建该字段。

{
  "key_1": 1
}

再次启用upsert并运行该操作。该array字段是在第二个查询中创建的:

{
  "key_1": 1,
  "array": [
    {
      "key_2": 1,
      "counter" 1
    }
  ]
}

再次运行该操作两次。第一个查询修改了文档两次:

{
  "key_1": 1,
  "array": [
    {
      "key_2": 1,
      "counter" 3
    }
  ]
}

再次运行该操作。第一个查询不会修改文档。第二个查询创建一个副本:

{
  "key_1": 1,
  "array": [
    {
      "key_2": 1,
      "counter" 3
    }, {
      "key_2": 1,
      "counter" 1
    }
  ]
}

标签: node.jsmongodbmongodb-query

解决方案


请试试这个:

var key_2Value = 2;

var firstFilterQuery = {
    key_1: 1,
    array: {
        $elemMatch: {
            "key_2": key_2Value,
            "date": 'someDate',
            "conter": { $lte: 3 }
        }
    }
}

var secondFilterQuery = {
    key_1: 1,
    "array.key_2": {$ne: key_2Value}
}

var defaultDoc = {key_2 : key_2Value, "date": 'someDefaultDate',counter: 1}

询问 :

collection.bulkWrite([
            {
                updateOne:
                {
                    "filter": firstFilterQuery,
                    "update": { $inc: { "array.$.conter": 1 } }
                }
            }, {
                updateOne:
                {
                    "filter": secondFilterQuery,
                    "update": { $push: { array: defaultDoc } 
                    }
                }
            }
        ])

使用上面的查询,您可以在一个 DB 调用中实现您想要的(在任何给定情况下,只有一个“updateOne”应该更新 DB),输出应该类似于:

{
    "acknowledged" : true,
    "deletedCount" : 0.0,
    "insertedCount" : 0.0,
    "matchedCount" : 1.0,
    "upsertedCount" : 0.0,
    "insertedIds" : {},
    "upsertedIds" : {}
}

推荐阅读