嵌套数组的MongoDB $ inc和$ setOnInsert

时间:2017-01-20 10:41:11

标签: javascript mongodb mongoose

我搜索了SO并抓取了Mongoose / Mongo文档,但没有用,所以我的问题。

我想$inc位于嵌套数组中的对象中的值,或者如果该对象尚未存在则创建$setOnInsert

我看到的Mongo文件如下:

{
  "_id": "123",
  "members": [
    {
      "first": "johnny",
      "last": "knoxville",
      "score": 2
    },
    {
      "first": "johnny",
      "last": "cash",
      "score": 3
    },
    // ...and so on
  ],
}

基于此示例,我的用例是:

  • 如果数组对象中存在count变量(如基于firstlast找到),则会增加该变量
  • 如果对象
  • ,则将对象添加到score为1的集合中

来自this post我明白我不能$set我想同时$inc变量$。好的 - 这是有道理的。

This post帮助理解了位置myDoc = { first: 'johnny', last: 'cash' }; myCollection.findOneAndUpdate( { _id: '123', 'members.first': 'johnny', 'members.last': 'cash' }, { $inc: { "members.$.score": 1 } } ); 运算符,以便找到嵌套对象并将其递增。

如果我知道该文档存在,我可以按如下方式进行更新:

score: 1

但是如果我想插入会员({{1}}),如果它还不存在怎么办?

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

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

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

1 个答案:

答案 0 :(得分:0)

在MongoDB 4.2和更高版本中,更新方法现在可以获取文档或an aggregate pipeline,其中可以使用以下阶段:

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

基于以上所述,您对聚合管道的更新操作将根据条件(即模板如下)覆盖members字段:

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
                }
            }
        }
    ]
);

要获取第一个条件的表达式 members数组是否包含myDoc?,您需要一种方法来满足满足以下条件的第一个和最后一个属性$filter分别与myDoc相同的值,即

{
    "$filter": {
        "input": "$members",
        "cond": {
            "$and": [
                { "$eq": ["$$this.first", myDoc.first] },
                { "$eq": ["$$this.last", myDoc.last] },
            ]
        }
    }
}

使用$arrayElemAt

检查结果数组的第一个元素
{
    "$arrayElemAt": [
        {
            "$filter": {
                "input": "$members",
                "cond": {
                    "$and": [
                        { "$eq": ["$$this.first", myDoc.first] },
                        { "$eq": ["$$this.last", myDoc.last] },
                    ]
                }
            }
        },
        0
    ]
}

如果没有匹配项,则上述内容将为null,我们可以用$ifNull用作主要条件的值替换该null:

{
    "$ifNull": [
        {
            "$arrayElemAt": [
                {
                    "$filter": {
                        "input": "$members",
                        "cond": {
                            "$and": [
                                { "$eq": ["$$this.first", myDoc.first] },
                                { "$eq": ["$$this.last", myDoc.last] },
                            ]
                        }
                    }
                },
                0
            ]
        },
        0
    ]   
}

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

{ "$ne": [
    {
        "$ifNull": [
            {
                "$arrayElemAt": [
                    {
                        "$filter": {
                            "input": "$members",
                            "cond": {
                                "$and": [
                                    { "$eq": ["$$this.first", myDoc.first] },
                                    { "$eq": ["$$this.last", myDoc.last] },
                                ]
                            }
                        }
                    },
                    0
                ]
            },
            0
        ]   
    },
    0
] }

如果以上条件为真,则$map表达式变为

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

    }
}

否则用$concatArrays

将新文档添加到现有成员数组中
{
    "$concatArrays": [
        { "$ifNull": ["$members", []] },
        [ myDoc ]
    ]
}

您的最终更新操作将变为:

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 } 
);