mongoose 如何更新内嵌数组对象的数据? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ideacco
V2EX    MongoDB

mongoose 如何更新内嵌数组对象的数据?

  •  
  •   ideacco 2020-09-29 11:01:05 +08:00 4939 次点击
    这是一个创建于 1837 天前的主题,其中的信息可能已经有所发展或是发生改变。

    折腾了一晚上,文档看完了,谷歌操作一顿,没搞定。只能求助大神啦。

    我有一个数据表 Schema 大概如下:

    // PostCountry 表 const PostCountrySchema = new Schema({ uid: { type: ObjectId, ref: 'User', index: true, required: true }, pid: { type: String, ref: 'Product', index: { unique: true }, // 设为唯一的 required: true }, country_list: [ { country_name: { type: String, required: true }, country_code: String, fourPx_code: { type: String, default: '' }, declared: { // 申报单价 type: Number, default: 0 }, fourPx_send_name: { type: String, default: '' } } ] } 

    数据示例如下:

    // PostCountry 表 "country_list": [ { "country_name": "China", "declared": 3, "fourPx_send_name": "中国邮政" }, { "country_name": "Ecuador", "declared": 21, "fourPx_send_name": "中国邮政" }, { "country_name": "Argentina", "declared": 14, "fourPx_send_name": "中国邮政" } ], "pid": "5380578836639", "uid": "5f7205b75e08ebr1dab5abfa" 

    我现在需要更新 PostCountry 表中嵌套数组的,fourPx_send_name 数据,如果数据存在则更新,如果不存在则在这个表内增加新的数组。

    我 post 填充的数据是:

    { "country_name": "Canada", "declared": 123, "fourPx_send_name": "中国邮 31231 政", "pid":"5380578836639" } 

    我用过的方法 1:

    const Data = await PostCountrySchema.findOneAndUpdate({ uid: ctx.cookies.get('uid'), pid: psotData.pid, country_list: { $elemMatch: { country_name: psotData.country_name } } }, { $set: { 'country_list.$.fourPx_send_name': psotData.fourPx_send_name, 'country_list.$.declared': psotData.declared } }, { new: true, upsert: true, setDefaultsOnInsert: true }) .then((data) => { return data }) .catch(err => console.error('返回错误' + err)) 

    这样操作会返回一个错误 MongoError: Updating the path 'country_list.$.fourPx_send_name' would create a conflict at 'country_list'

    我用过的方法 2:

    const Data = await PostCountrySchema.findOneAndUpdate({ uid: ctx.cookies.get('uid'), pid: psotData.pid, 'country_list.country_name': { $ne: psotData.country_name } }, { $addToSet: { country_list: { country_name: psotData.country_name, fourPx_send_name: psotData.fourPx_send_name, declared: psotData.declared } } }, { new: true, upsert: true, setDefaultsOnInsert: true }) .then((data) => { return data }) .catch(err => console.error('返回错误' + err)) 

    这样操作,如果 country_name 字段名字没搜索到,是可以添加成功的,然而再次提交修改就会报错了:

    MongoError: E11000 duplicate key error collection: shopify.post_country index: pid_1 dup key: { pid: "5380578836639" }

    操作 3,将 上面的 $addToSet 改成$push 结果也是一样,没有数据可以填充,有数据就无法填充了。

    如何才可以根据条件更新内嵌数组的数据,如无数据则新增数组,如果有数据则更新数组?

    折磨了半天了,求大神支个招吧。

    7 条回复    2020-09-30 11:52:24 +08:00
    libook
        1
    libook  
       2020-09-29 13:31:24 +08:00
    https://docs.mongodb.com/manual/reference/operator/update/addToSet/#value-to-add-is-a-document
    $addToSet 必须得是字段和值完全一样才会认为不需要插入,也就是说顶多做插入,跟更新数据没关系。

    你要是非要按照现有的 schema 来做,只能用程序先遍历 country_list,找到一样的 fourPx_send_name 就修改数据,然后整个 document save 。

    MongoDB 操作的最小单位是文档(Document),你现在操作的是子文档,所以会很麻烦。

    对于需要频繁对你现在操作的是子文档操作的场景,应该把你现在操作的是子文档抽出来做成一个新的 Collection,然后用 ref 和 populate 来与 PostCountry 建立关系。
    libook
        2
    libook  
       2020-09-29 13:32:49 +08:00   2
    对于需要频繁操作子文档的场景,应该把子文档抽出来做成一个新的 Collection,然后用 ref 和 populate 来与 PostCountry 建立关系。
    ideacco
        3
    ideacco  
    OP
       2020-09-29 13:35:59 +08:00
    @libook 非常感谢您的回复
    ideacco
        4
    ideacco  
    OP
       2020-09-29 18:43:09 +08:00
    @libook 又捣鼓了半天,还是不行啊,郁闷了
    libook
        5
    libook  
       2020-09-30 11:01:02 +08:00
    // const body = {
    // "country_name": "Canada",
    // "declared": 123,
    // "fourPx_send_name": "中国邮 31231 政",
    // "pid": "5380578836639"
    // }
    // const postCountry = await PostCountry.findOne({ "pid": body.pid });
    // if (postCountry !== null) {
    // let isCountryExists = false;
    // for (const index in postCountry.country_list) {
    // if (postCountry.country_list[index].fourPx_send_name === body.fourPx_send_name) {
    // postCountry.country_list[index] = body;
    // isCountryExists = true;
    // break; // 不需要继续循环了
    // }
    // }
    // if (!isCountryExists) {
    // // 如果没有找到相同的 country 信息,就插入
    // postCountry.country_list.push(body);
    // // 如果 country_list 部分的 schema 用了 Mixed,就得需要调用 markModified,如果 schema 里声明的就是对象数组就不需要
    // }
    // await postCountry.save();
    // } else {
    // // pid 没找到相关 document
    // }

    就是查出来,然后判断数组和修改,再 save 进去。

    另外因为 findOne 和 save 是两步操作,如果是分布式高并发系统可能在这个过程中这个 document 就已经被其他实例修改过了,所以需要用两段提交或事务来保证整个过程的原子性,MongoDB 都支持。
    libook
        6
    libook  
       2020-09-30 11:02:25 +08:00   1
    以为改成注释格式就不会乱了,没想到 V2 直接把行内多个空格删掉了,你自己贴到编辑器里,反注释再格式化一下看吧
    ideacco
        7
    ideacco  
    OP
       2020-09-30 11:52:24 +08:00
    @libook 太感谢你啦,兄弟
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     892 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 20:54 PVG 04:54 LAX 13:54 JFK 16:54
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86