V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐工具
RoboMongo
推荐书目
50 Tips and Tricks for MongoDB Developers
Related Blogs
Snail in a Turtleneck
ideacco
V2EX  ›  MongoDB

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

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

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

    我有一个数据表 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   ·   实用小工具   ·   849 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 20:10 · PVG 04:10 · LAX 12:10 · JFK 15:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.