簡體   English   中英

MongoDB 有條件地按特定字段在數組中設置 $addToSet 子文檔

[英]MongoDB conditionally $addToSet sub-document in array by specific field

有沒有辦法根據數組子文檔中的特定鍵字段有條件地$addToSet

這是我的意思的一個例子 - 鑒於以下示例引導程序生成的集合;

cls
db.so.remove();
db.so.insert({
    "Name": "fruitBowl",
    "pfms" : [
        {
            "n" : "apples"
        }
    ]
});

n定義了唯一的文檔鍵。 在任何時候,我只希望數組中有一個具有相同n值的條目。 所以我希望能夠使用n更新pfms數組,以便我最終得到這個;

{
    "Name": "fruitBowl",
    "pfms" : [
        {
            "n" : "apples",
            "mState": 1111234
        }
    ]
}

這就是我現在所處的位置;

db.so.update({
  "Name": "fruitBowl",
},{
// not allowed to do this of course
//    "$pull": {
//  "pfms": { n: "apples" },
//    },

"$addToSet": {
  "pfms": {
    "$each": [
      {
        "n": "apples",
        "mState": 1111234
      }
    ]
   }
  }
 }
)

不幸的是,這增加了另一個數組元素;

db.so.find().toArray();

[
    {
        "Name" : "fruitBowl",
        "_id" : ObjectId("53ecfef5baca2b1079b0f97c"),
        "pfms" : [
            {
                "n" : "apples"
            },
            {
                "n" : "apples",
                "mState" : 1111234
            }
        ]
    }
]

我需要有效地將在n匹配的apples文檔更新為唯一標識符,並設置mState是否已存在條目。 很遺憾我不能在同一個文檔中執行$pull$addToSet (我試過)。

我在這里真正需要的是字典語義,但這不是現在的選擇,也不是打破文檔 - 任何人都可以想出另一種方式嗎?

FWIW - 現有格式是語言/驅動程序序列化的結果,我沒有完全選擇它。

更遠

在我知道數組元素已經存在的情況下,我可以做到這一點;

db.so.update({
    "Name": "fruitBowl",
    "pfms.n": "apples",
},{
  $set: {
   "pfms.$.mState": 1111234,
  },
 }
)

但當然這只是有效;

  1. 對於單個數組元素
  2. 只要我知道它存在

第一個限制不是災難,但如果我不能有效地將$addToSet與前一個$set合並或合並(當然我不能),那么我現在能想到的唯一解決方法意味着兩個 DB 輪- 旅行。

$addToSet運算符當然要求被“添加到集合中”的“整個”文檔實際上是唯一的,因此您不能更改文檔的“部分”或以其他方式將其視為“部分匹配”。

您偶然發現了使用$pull刪除任何帶有“key”字段的元素會導致“重復”的最佳方法,但當然您不能像這樣在不同的更新運算符中修改相同的路徑。

因此,您將得到的最接近的事情是發出單獨的操作,但也使用 MongoDB 2.6 引入的“批量操作 API”來執行此操作 這允許將兩者同時發送到服務器,以獲得與您將獲得的“連續”操作列表最接近的內容:

var bulk = db.so.initializeOrderedBulkOp();

bulk.find({ "Name": "fruitBowl", "pfms.n": "apples": }).updateOne({ 
    "$pull": { "pfms": { "n": "apples" } }
});
bulk.find({ "Name": "fruitBowl" }).updateOne({
    "$push": { "pfms": { "n": "apples", "state": 1111234 } }
})

bulk.execute();

如果將元素移動到另一個集合並依賴“upserts”和$set以具有相同的功能但依賴於集合而不是數組是不可能或不切實際的,那么這幾乎是您最好的方法。

我遇到了完全相同的情況。 我正在從帖子中插入和刪除喜歡。

我所做的是使用 mongoose findOneAndUpdate 函數(類似於 mongodb 中的 update 或 findAndModify 函數)。

關鍵概念是

  • 當字段存在時插入
  • 刪除當字段存在

插入是

findOneAndUpdate({ _id: theId, 'likes.userId': { $ne: theUserId }},
{ $push: { likes: { userId: theUserId, createdAt: new Date() }}},
{ 'new': true }, function(err, post) { // do the needful }); 

刪除是

findOneAndUpdate({ _id: theId, 'likes.userId': theUserId},
{ $pull: { likes: { userId: theUserId }}},
{ 'new': true }, function(err, post) { // do the needful }); 

這使得整個操作具有原子性,並且沒有關於userId字段的重復項。

我希望這會有所幫助。 如果您有任何疑問,請隨時提出。

作為一名業務分析師,我遇到了同樣的問題,希望經過數小時的調查后我能找到解決方案。

// The customer document: 

{
   "id" : "1212",
    "customerCodes" : [ 
        {
            "code" : "I"          
        },
        {
            "code" : "YK"          
        }        
    ]
}

// 問題:我想將 dateField "01.01.2016" 插入到 customerCodes 子文檔具有代碼“YK”但沒有 dateField 的文檔的客戶文檔中。 最終文件必須如下:

{
   "id" : "1212",
    "customerCodes" : [ 
        {
            "code" : "I"          
        },
        {
            "code" : "YK" , 
            "dateField" : "01.01.2016"
        }        
    ]
}

// 解決方案:解決方案代碼分三步:

// 第 1 部分 - 查找帶有 customerCodes“YK”但沒有 dateField 的客戶

// 第 2 部分 - 在 customerCodes 列表中查找帶有“YK”的子文檔的索引。

// PART 3 - 將值插入到文檔中

// 這里是代碼

    // PART 1 
    var myCursor = db.customers.find({ customerCodes:{$elemMatch:{code:"YK", dateField:{ $exists:false} }}});

    // PART 2 
    myCursor.forEach(function(customer){       
         if(customer.customerCodes != null ) 
         {           
            var size = customer.customerCodes.length;  
            if( size > 0 )  
            {
                var iFoundTheIndexOfSubDocument= -1; 
                var index = 0;     
                customer.customerCodes.forEach( function(clazz)
                {                           
                    if(  clazz.code == "YK" && clazz.changeDate == null ) 
                    {
                        iFoundTheIndexOfSubDocument = index;                 

                    }
                    index++;  
                }) 

                // PART 3
                // What happens here is : If i found the indice of the 
                // "YK" subdocument, I create "updates" document which   
//                 corresponds to the new data to be inserted`                       
                //
                if( iFoundTheIndexOfSubDocument != -1 ) 
                {
                    var toSet = "customerCodes."+ iFoundTheIndexOfSubDocument +".dateField";
                    var updates = {};
                    updates[toSet] = "01.01.2016";

                    db.customers.update({ "id" : customer.id } , { $set: updates }); 
    // This statement is actually interpreted like this : 
    // db.customers.update({ "id" : "1212" } ,{ $set: customerCodes.0.dateField : "01.01.2016" });
                }
            }                                       
         }
    }); 

祝你今天過得愉快 !

據我所知,MongoDB 現在(從v 4.2 )允許使用聚合管道進行更新 使其工作的或多或少優雅的方式(根據問題)如下所示:

db.runCommand({
  update: "your-collection-name",
  updates: [
    {
      q: {},
      u: {
        $set: {
          "pfms.$[elem]": {
            "n":"apples",
            "mState": NumberInt(1111234)
          }
        }
      },
      arrayFilters: [
        {
          "elem.n": {
            $eq: "apples"
          }
        }
      ],
      multi: true
    }
  ]
})

在我的場景中,數據不存在時需要初始化,如果存在則更新字段,數據不會被刪除。 如果數據有這些狀態,您可能想嘗試以下方法。

// Mongoose, but mostly same as mongodb
    // Update the tag to user, If there existed one.
    const user = await UserModel.findOneAndUpdate(
      {
        user: userId,
        'tags.name': tag_name,
      },
      {
        $set: {
          'tags.$.description': tag_description,
        },
      }
    )
      .lean()
      .exec();

    // Add a default tag to user
    if (user == null) {
      await UserModel.findOneAndUpdate(
        {
          user: userId,
        },
        {
          $push: {
            tags: new Tag({
              name: tag_name,
              description: tag_description,
            }),
          },
        }
      );
    }

這是場景中最干凈、最快速的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM