嵌套数组的MongoDB $ inc和$ setOnInsert

ebb*_* mo 5 javascript mongoose mongodb

我搜索了SO并抓取了Mongoose/Mongo文档,但无济于事,因此我的问题.

我想$inc在嵌套数组中的对象中的值,或者$setOnInsert如果它还没有,则创建该对象.

我看到的Mongo文件如下:

{
  "_id": "123",
  "members": [
    {
      "first": "johnny",
      "last": "knoxville",
      "score": 2
    },
    {
      "first": "johnny",
      "last": "cash",
      "score": 3
    },
    // ...and so on
  ],
}
Run Code Online (Sandbox Code Playgroud)

根据这个例子,我的用例是:

  • count如果数组对象中存在变量(基于first和找到last),则递增该变量
  • 如果对象score尚不存在,则将对象添加为1

这篇文章中我了解到我不能同时$set拥有一个我想要的变量$inc.好的 - 这很有道理.

这篇文章有助于理解位置$运算符,以便找到嵌套对象并增加它.

如果我知道该文档存在,我可以简单地进行如下更新:

myDoc = { first: 'johnny', last: 'cash' };

myCollection.findOneAndUpdate(
  {
    _id: '123',
    'members.first': 'johnny',
    'members.last': 'cash'
  },
  {
    $inc: { "members.$.score": 1 }
  }
);
Run Code Online (Sandbox Code Playgroud)

但是如果我想插入成员(score: 1如果它还不存在)怎么办?

我的问题是,当我使用upsert:true时,位置运算符会抛出错误,因为它可能不会与upsert一起使用(请参阅官方文档).

我尝试了各种组合,并希望避免2 db访问(读/写).

有没有办法在一次操作中执行此操作?

chr*_*dam 2

在 MongoDB 4.2 及更高版本中,更新方法现在可以采用文档或聚合管道,其中可以使用以下阶段:

  1. $addFields及其别名$set
  2. $project及其别名$unset
  3. $replaceRoot及其别名$replaceWith

有了上述内容,您对聚合管道的更新操作将是根据条件覆盖成员字段,即模板如下:

var myDoc = { first: 'johnny', last: 'cash', score: 1 };

db.getCollection('myCollection').update(
    { "_id" : "123" },
    [
        "$set": {
            "members": {
                "$cond": {
                    "if": {}, // does the members array contain myDoc?
                    "then": {}, // map the members array and increment the score
                    "else": {} // add myDoc to the existing members array
                }
            }
        }
    ]
);
Run Code Online (Sandbox Code Playgroud)

要获取第一个条件的表达式,成员数组是否包含 myDoc?,您需要一种$filter基于满足第一个和最后一个属性具有相同值的条件的数组方法myDoc,即

{
    "$filter": {
        "input": "$members",
        "cond": {
            "$and": [
                { "$eq": ["$$this.first", myDoc.first] },
                { "$eq": ["$$this.last", myDoc.last] },
            ]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

检查结果数组的第一个元素$arrayElemAt

{
    "$arrayElemAt": [
        {
            "$filter": {
                "input": "$members",
                "cond": {
                    "$and": [
                        { "$eq": ["$$this.first", myDoc.first] },
                        { "$eq": ["$$this.last", myDoc.last] },
                    ]
                }
            }
        },
        0
    ]
}
Run Code Online (Sandbox Code Playgroud)

如果没有匹配,那么上面的内容将为 null,我们可以用一个可以用作主要条件的值来替换 null $ifNull

{
    "$ifNull": [
        {
            "$arrayElemAt": [
                {
                    "$filter": {
                        "input": "$members",
                        "cond": {
                            "$and": [
                                { "$eq": ["$$this.first", myDoc.first] },
                                { "$eq": ["$$this.last", myDoc.last] },
                            ]
                        }
                    }
                },
                0
            ]
        },
        0
    ]   
}
Run Code Online (Sandbox Code Playgroud)

以上成为我们用于$ne检查不等式的第一个 IF 语句的条件的基础:

{ "$ne": [
    {
        "$ifNull": [
            {
                "$arrayElemAt": [
                    {
                        "$filter": {
                            "input": "$members",
                            "cond": {
                                "$and": [
                                    { "$eq": ["$$this.first", myDoc.first] },
                                    { "$eq": ["$$this.last", myDoc.last] },
                                ]
                            }
                        }
                    },
                    0
                ]
            },
            0
        ]   
    },
    0
] }
Run Code Online (Sandbox Code Playgroud)

如果上述条件成立,则$map表达式变为

{                        
    "$map": {
        "input": "$members",
        "in": {
            "$cond": [
                { "$eq": [{ "$ifNull": ["$$this.score", 0 ] }, 0] },
                { "$mergeObjects": ["$$this", { "score": 1 } ] },
                { "$mergeObjects": ["$$this", { "score": { "$sum": ["$$this.score", 1] } } ] }
            ]
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

否则将新文档添加到现有成员数组中$concatArrays

{
    "$concatArrays": [
        { "$ifNull": ["$members", []] },
        [ myDoc ]
    ]
}
Run Code Online (Sandbox Code Playgroud)

您的最终更新操作变为:

var myDoc = { first: 'johnny', last: 'cash', score: 1 };

db.getCollection("myCollection").update(
    { "_id" : "123" },
    [
        { "$set": {
            "members": {
                "$cond": {
                    "if": {
                        "$ne": [
                            {
                                "$ifNull": [
                                    {
                                        "$arrayElemAt": [
                                            {
                                                "$filter": {
                                                    "input": "$members",
                                                    "cond": {
                                                        "$and": [
                                                            { "$eq": ["$$this.first", myDoc.first] },
                                                            { "$eq": ["$$this.last", myDoc.last] },
                                                        ]
                                                    }
                                                }
                                            },
                                            0
                                        ]
                                    },
                                    0
                                ]   
                            },
                            0
                        ]
                    },
                    "then": {
                        "$map": {
                            "input": "$members",
                            "in": {
                                "$cond": [
                                    { "$eq": [{ "$ifNull": ["$$this.score", 0 ] }, 0] },
                                    { "$mergeObjects": ["$$this", { "score": 1 } ] },
                                    { "$mergeObjects": ["$$this", { "score": { "$sum": ["$$this.score", 1] } } ] }
                                ]
                            }

                        }
                    },
                    "else": {
                        "$concatArrays": [
                            { "$ifNull": ["$members", []] },
                            [ myDoc ]
                        ]
                    }
                }
            }
        } }
    ],
    { "upsert": true } 
);
Run Code Online (Sandbox Code Playgroud)