MongoDB 更新篇

上一篇文檔,我們說了基本的MongoDB命令。接下來,我們要重點寫一下,更新的操作。MongoDB的更新非常多樣靈活,基本上可以應對日常上大部分的使用場景,下面我將會一一細說。


1、基本的文檔替換更新

直接通過update方法更新整份文檔

db.product.update({product_name:"iPhoneX"},
{product_name:"iPhoneX",price:9100,description:"一臺貴到666的手機",product_number:"9830829131",brand:"蘋果"});


2、$inc 修改器

剛剛那個商品記錄,其實我們只希望調整價格而已。但是我們卻需要更新一份文檔,事實我們可以使用$inc修改器去調整價格

db.product.update({product_name:"iPhoneX"},{$inc:{"price":500}});

通過$inc 修改器爲price屬性字段增加500塊,當然如果我們需要減價199也可以使用負數進行遞減:

db.product.update({product_name:"iPhoneX"},{$inc:{price:-199}});


3、$set修改器

$set修改器可以針對文檔中某個字段進行修改,例如我們可以修改剛剛那個文檔的brand,我們改成Apple

db.product.update({product_name:"iPhoneX"},{$set:{"brand":"Apple"}});

其實我們還可以通過$set修改器,添加新的字段:

db.product.update({product_name:"iPhoneX"},{$set:{"size":"143.6 X 70.9"}});

修改結果如下:

{ 
    "_id" :ObjectId("59e47fdb203e071a1b02e544"), 
    "product_name" : "iPhoneX", 
    "price" :9499.0, 
    "description" :"一臺貴到666的手機", 
    "product_number" : "9830829131", 
    "brand" :"Apple", 
    "size" :"143.6 X 70.9"
}

當然我們也可以設置內嵌的文檔,例如我希望添加最近一條的評論。

var newComment = {comment_content:"TEST NEW COMMENT",user_id:109382,create_date:new Date()};
db.product.update({product_name:"iPhoneX"},{$set:{newest_commment:newComment}});
db.product.findOne();
db.product.update({product_name:"iPhoneX"},{$set:{"newest_commment.comment_content":"this is newtest comment at that place!"}});
db.product.findOne();
需要注意的是,之前我一直沒有再key當中打雙引號是不規範的,如果需要調用內嵌文檔(內嵌對象)的時候必須要在key寫上雙引號,不然無法執行。

結果如下:

{ 
    "_id" : ObjectId("59e47fdb203e071a1b02e544"), 
    "product_name" : "iPhoneX", 
    "price" : 9499.0, 
    "description" : "一臺貴到666的手機", 
    "product_number" : "9830829131", 
    "brand" : "Apple", 
    "size" : "143.6 X 70.9", 
    "newest_commment" : {
        "comment_content" : "this is newtest comment at that place!", 
        "user_id" : 109382.0, 
        "create_date" : ISODate("2017-10-17T01:15:20.466+0000")
    }
}


4、數組修改器

我們希望在商品上添加規格參數,我們可以數組的方式對規格對象進行文檔內嵌。但是規格會有多個,我們可以在文檔中通過數組類型進行存儲

我們可以使用$push 爲文檔中的數組屬性的結尾添加一個新的item,如果$push的屬性不存在,就會創建一個數組並添加你要添加的數據:

var iphonexSku64GSilver={capacity:"64G",style:"silver"};
var iphonexSku64GGray={capacity:"64G",style:"gray"};
db.product.update({product_name:"iPhoneX"},{$push:{sku:iphonexSku64GSilver}});
db.product.update({product_name:"iPhoneX"},{$push:{sku:iphonexSku64GGray}});
db.product.findOne();
結果:

{ 
    "_id" : ObjectId("59e47fdb203e071a1b02e544"), 
    "product_name" : "iPhoneX", 
    "price" : 9300.0, 
    "description" : "一臺貴到666的手機", 
    "product_number" : "9830829131", 
    "brand" : "Apple", 
    "size" : "143.6 X 70.9", 
    "newest_commment" : {
        "comment_content" : "this is newtest comment at that place!", 
        "user_id" : 109382.0, 
        "create_date" : ISODate("2017-10-17T01:15:20.466+0000")
    }, 
    "sku" : [
        {
            "capacity" : "64G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "64G", 
            "style" : "gray"
        }
    ]
}
如果我們需要連續添加兩個item的話,我們還需要藉助$each這個子操作符

下面我們就通過$push 和 $each 配合連續添加連個SKU,這次是256G的

db.product.update({product_name:"iPhoneX"},{$push:{sku:{$each:[iphonexSku256GSilver,iphonexSku256GGray]}}});
db.product.findOne();
結果如下:

{ 
    "_id" : ObjectId("59e47fdb203e071a1b02e544"), 
    "product_name" : "iPhoneX", 
    "price" : 9300.0, 
    "description" : "一臺貴到666的手機", 
    "product_number" : "9830829131", 
    "brand" : "Apple", 
    "size" : "143.6 X 70.9", 
    "newest_commment" : {
        "comment_content" : "this is newtest comment at that place!", 
        "user_id" : 109382.0, 
        "create_date" : ISODate("2017-10-17T01:15:20.466+0000")
    }, 
    "sku" : [
        {
            "capacity" : "64G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "64G", 
            "style" : "gray"
        }, 
        {
            "capacity" : "256G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "256G", 
            "style" : "gray"
        }
    ]
}
有時我們希望$each添加數組的時候,希望得到長度的限制,例如$each 裏面有5個item,但是我只需要3個item。爲了我越界,我們可以通過$slice保留最後3個item,而且我們還可以使用$sort對數組進行先排序然後再截取最後3個item。

var commentA = {content:"Comment A content !",user_id:399,popularity_degree:89};
var commentB = {content:"Comment B content !",user_id:349,popularity_degree:39};
var commentC = {content:"Comment C content !",user_id:119,popularity_degree:99};
var commentD = {content:"Comment D content !",user_id:334,popularity_degree:76};
var commentE = {content:"Comment E content !",user_id:893,popularity_degree:103};
var commentArray = [commentA,commentB,commentC,commentD,commentE];
db.product.update({product_name:"iPhoneX"},
	{$push:
	  {popular_comment:
	    	{$each:commentArray,
	    	  $slice:-3,
	    	  $sort:{"popularity_degree":1}
	    	}
	  }
	 });
db.product.findOne();
插入結果:

{ 
    "_id" : ObjectId("59e47fdb203e071a1b02e544"), 
    "product_name" : "iPhoneX", 
    "price" : 9300.0, 
    "description" : "一臺貴到666的手機", 
    "product_number" : "9830829131", 
    "brand" : "Apple", 
    "size" : "143.6 X 70.9", 
    "newest_commment" : {
        "comment_content" : "this is newtest comment at that place!", 
        "user_id" : 109382.0, 
        "create_date" : ISODate("2017-10-17T01:15:20.466+0000")
    }, 
    "sku" : [
        {
            "capacity" : "64G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "64G", 
            "style" : "gray"
        }, 
        {
            "capacity" : "256G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "256G", 
            "style" : "gray"
        }
    ], 
    "popular_comment" : [
        {
            "content" : "Comment A content !", 
            "user_id" : 399.0, 
            "popularity_degree" : 89.0
        }, 
        {
            "content" : "Comment C content !", 
            "user_id" : 119.0, 
            "popularity_degree" : 99.0
        }, 
        {
            "content" : "Comment E content !", 
            "user_id" : 893.0, 
            "popularity_degree" : 103.0
        }
    ]
}

5、數組作爲SET集合

通常我們也有使用SET集合的場景,例如keyword,不希望每次添加的商品關鍵字會有重複,這樣不利於查詢。但是MongoDB只有數組類型,所以我們需要藉助$addToSet去完成這一任務。

db.product.update({product_name:"iPhoneX"},{$addToSet:{keyword:"iphone"}});
db.product.findOne();
db.product.update({product_name:"iPhoneX"},{$addToSet:{keyword:{$each:["iphone","蘋果","apple"]}}});
db.product.findOne();
結果:

{ 
    "_id" : ObjectId("59e47fdb203e071a1b02e544"), 
    "product_name" : "iPhoneX", 
    "price" : 9300.0, 
    "description" : "一臺貴到666的手機", 
    "product_number" : "9830829131", 
    "brand" : "Apple", 
    "size" : "143.6 X 70.9", 
    "newest_commment" : {
        "comment_content" : "this is newtest comment at that place!", 
        "user_id" : 109382.0, 
        "create_date" : ISODate("2017-10-17T01:15:20.466+0000")
    }, 
    "sku" : [
        {
            "capacity" : "64G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "64G", 
            "style" : "gray"
        }, 
        {
            "capacity" : "256G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "256G", 
            "style" : "gray"
        }
    ], 
    "popular_comment" : [
        {
            "content" : "Comment A content !", 
            "user_id" : 399.0, 
            "popularity_degree" : 89.0
        }, 
        {
            "content" : "Comment C content !", 
            "user_id" : 119.0, 
            "popularity_degree" : 99.0
        }, 
        {
            "content" : "Comment E content !", 
            "user_id" : 893.0, 
            "popularity_degree" : 103.0
        }
    ], 
    "keyword" : [
        "iphone", 
        "蘋果", 
        "apple"
    ]
}

6、刪除數組元素

刪除其中一個數組元素,我們可以通過$pop刪除數組頭部或者尾部的元素

db.product.update({product_name:"iPhoneX"},{$pop:{keyword:1}});
db.product.update({product_name:"iPhoneX"},{$pop:{keyword:-1}});

value 爲 1 的時候刪除尾部第一個元素,value爲-1時刪除頭部第一個元素。


7、基於位置的數組修改

我們也可以通過數組的下標去,修改數組中的元素。

db.product.update({product_name:"iPhoneX"},{$set:{"popular_comment.1.popularity_degree":108}});
db.product.findOne();

上面就不用怎麼解釋了,就是修改popular_comment中的第二個元素的populartity_degree屬性

結果:

{ 
    "_id" : ObjectId("59e47fdb203e071a1b02e544"), 
    "product_name" : "iPhoneX", 
    "price" : 9300.0, 
    "description" : "一臺貴到666的手機", 
    "product_number" : "9830829131", 
    "brand" : "Apple", 
    "size" : "143.6 X 70.9", 
    "newest_commment" : {
        "comment_content" : "this is newtest comment at that place!", 
        "user_id" : 109382.0, 
        "create_date" : ISODate("2017-10-17T01:15:20.466+0000")
    }, 
    "sku" : [
        {
            "capacity" : "64G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "64G", 
            "style" : "gray"
        }, 
        {
            "capacity" : "256G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "256G", 
            "style" : "gray"
        }
    ], 
    "popular_comment" : [
        {
            "content" : "Comment A content !", 
            "user_id" : 399.0, 
            "popularity_degree" : 89.0
        }, 
        {
            "content" : "Comment C content !", 
            "user_id" : 119.0, 
            "popularity_degree" : 108.0
        }, 
        {
            "content" : "Comment E content !", 
            "user_id" : 893.0, 
            "popularity_degree" : 103.0
        }
    ], 
    "keyword" : [
        "蘋果", 
        "iphone", 
        "apple"
    ]
}


還有一種就是通過修改匹配條件的時候直接定位到要改的座標,很難說的明白,看看例子:

db.product.update({"sku.capacity":"64G"},{$set:{"sku.$.price":8599}});
db.product.findOne();
通過結果可以發現,只有第一個匹配的元素纔會修改。事實上我們的匹配添加應該匹配到兩個元素,但是實際修改只有匹配到第一個元素

{ 
    "_id" : ObjectId("59e47fdb203e071a1b02e544"), 
    "product_name" : "iPhoneX", 
    "price" : 9300.0, 
    "description" : "一臺貴到666的手機", 
    "product_number" : "9830829131", 
    "brand" : "Apple", 
    "size" : "143.6 X 70.9", 
    "newest_commment" : {
        "comment_content" : "this is newtest comment at that place!", 
        "user_id" : 109382.0, 
        "create_date" : ISODate("2017-10-17T01:15:20.466+0000")
    }, 
    "sku" : [
        {
            "capacity" : "64G", 
            "style" : "silver", 
            "price" : 8599.0
        }, 
        {
            "capacity" : "64G", 
            "style" : "gray"
        }, 
        {
            "capacity" : "256G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "256G", 
            "style" : "gray"
        }
    ], 
    "popular_comment" : [
        {
            "content" : "Comment A content !", 
            "user_id" : 399.0, 
            "popularity_degree" : 89.0
        }, 
        {
            "content" : "Comment C content !", 
            "user_id" : 119.0, 
            "popularity_degree" : 108.0
        }, 
        {
            "content" : "Comment E content !", 
            "user_id" : 893.0, 
            "popularity_degree" : 103.0
        }
    ], 
    "keyword" : [
        "蘋果", 
        "iphone", 
        "apple"
    ]
}



9、修改數據導致文檔大小經常改變,然而再導致大量的移動數據或者經常被打亂數據,使用usePowerOf2Size可以提高磁盤的服用率

db.runCommand({"collMod":product,"usePowerOf2Sizes":true});
啓動之後塊的大小都是2的冪,導致初始空間分配的時候沒有那麼高效了。原地更新集合或者插入會導致寫入速度會變慢,所以只適合於文檔大小經常被該表導致大量移動的collection。

這裏我就把usePowerOf2Sizes禁用掉了。

db.runCommand({"collMod":product,"usePowerOf2Sizes":false});


10、upsert 更新

upsert是一種特殊的更新操作,如果沒有找到調整匹配的文檔,就會使用這個條件創建一個新的文檔,將修改的數據插入到這個新建的文檔當中。

我們這裏記錄一下product的訪問記錄,我們使用一個新的collection,product_vp。還是使用我們原來的update 方法,不過我們需要第三個參數,第三個參數我們寫上true代表我們使用upsert更新

var iphonex = db.product.findOne({product_name:"iPhoneX"});
db.product_pv.update({"product_id":iphonex._id},{$inc:{"pageviews":1}},true);
db.product_pv.findOne();
db.product_pv.update({"product_id":iphonex._id},{$inc:{"pageviews":1}},true);
db.product_pv.findOne();
結果:

{ 
    "_id" : ObjectId("59e59c198742e0735878414e"), 
    "product_id" : ObjectId("59e47fdb203e071a1b02e544"), 
    "pageviews" : 2.0
}
還有一種操作是$setOnInsert,如果文檔存在就不會插入或者修改。如果文檔不存在就會插入這個屬性

db.product_pv.update({},{$setOnInsert:{"create_at":new Date()}},true);
db.product_pv.findOne();


11、save

這個跟JPA/hibernate 的save 非常相似,如果對象不存在就新建,如果存在就更新以下就是更新。

主要是判斷對象是否存在_id字段的,如果有id就證明這個對象已經存在了,如果沒有id就等於不存在,就會去創建一個新的。

iphonex.price = iphonex.sku[0].price;
db.product.save(iphonex);
db.product.findOne();

結果,我上面的代碼同步對象中sku的價格和顯示的價格

{ 
    "_id" : ObjectId("59e47fdb203e071a1b02e544"), 
    "product_name" : "iPhoneX", 
    "price" : 8599.0, 
    "description" : "一臺貴到666的手機", 
    "product_number" : "9830829131", 
    "brand" : "Apple", 
    "size" : "143.6 X 70.9", 
    "newest_commment" : {
        "comment_content" : "this is newtest comment at that place!", 
        "user_id" : 109382.0, 
        "create_date" : ISODate("2017-10-17T01:15:20.466+0000")
    }, 
    "sku" : [
        {
            "capacity" : "64G", 
            "style" : "silver", 
            "price" : 8599.0
        }, 
        {
            "capacity" : "64G", 
            "style" : "gray"
        }, 
        {
            "capacity" : "256G", 
            "style" : "silver"
        }, 
        {
            "capacity" : "256G", 
            "style" : "gray"
        }
    ], 
    "popular_comment" : [
        {
            "content" : "Comment A content !", 
            "user_id" : 399.0, 
            "popularity_degree" : 89.0
        }, 
        {
            "content" : "Comment C content !", 
            "user_id" : 119.0, 
            "popularity_degree" : 108.0
        }, 
        {
            "content" : "Comment E content !", 
            "user_id" : 893.0, 
            "popularity_degree" : 103.0
        }
    ], 
    "keyword" : [
        "蘋果", 
        "iphone", 
        "apple"
    ]
}


12、更新多個文檔

我們在使用update方法的時候,事實上只會更新第一條匹配到條件的文檔,其他匹配到的文檔都不會更新,我們需要用到update方法當中的第四個參數

爲了演示測試,我會多創建一個iPhoneX的商品,並且爲所有商品添加店鋪標識,代表不同店鋪售賣的iPhone X。

//這是我們原來的iPhone X
var iphonex = db.product.findOne({product_name:"iPhoneX"});
iphonex.store = "官方自營店";
db.product.save(iphonex);
db.product.findOne();

//新增第三方渠道的iPhone X
var iphonexb = iphonex;
iphonexb._id = ObjectId();
db.product.insert(iphonexb);
iphonexb.store = "第三方渠道";
db.product.save(iphonexb);
db.product.findOne({_id:iphonexb._id});

我們將平臺上所有iPhone X的商品統一修改商品編碼:

db.product.update({"product_name":"iPhoneX"},{$set:{"product_number":"9088816371"}},false,true);
可以見看到返回的結果顯示,match兩條記錄,修改了兩條記錄

WriteResult({ "nMatched" : 2, "nUpserted" : 0, "nModified" : 2 })


13、獲得最後一次執行的文檔結果

通常我們需要獲得最後一次更新之後的文檔數據,保證原子性。

所以我們這裏可以用另外一個查詢方法,findAndModity

db.runCommand(
   {
     findAndModify: "product",
     query: { product_name: "iPhoneX" },
     sort: { price: 1 },
     update: { $inc: { price: 100 } },
     upsert: false
   }
 );

上面的代碼,應該也比較清楚的findAndModify的value是collection的名字,query是要匹配的文檔查詢條件,sort是排序方式,update就是更新文檔的操作,upsert就是不存在是否創建一個新的文檔。

結果就會馬上返回,不用再去調用findOne去查詢:

{ 
    "lastErrorObject" : {
        "updatedExisting" : true, 
        "n" : 1.0
    }, 
    "value" : {
        "_id" : ObjectId("59e5a20f7dfc75087c893b50"), 
        "product_name" : "iPhoneX", 
        "price" : 8599.0, 
        "description" : "一臺貴到666的手機", 
        "product_number" : "9088816371", 
        "brand" : "Apple", 
        "size" : "143.6 X 70.9", 
        "newest_commment" : {
            "comment_content" : "this is newtest comment at that place!", 
            "user_id" : 109382.0, 
            "create_date" : ISODate("2017-10-17T01:15:20.466+0000")
        }, 
        "sku" : [
            {
                "capacity" : "64G", 
                "style" : "silver", 
                "price" : 8599.0
            }, 
            {
                "capacity" : "64G", 
                "style" : "gray"
            }, 
            {
                "capacity" : "256G", 
                "style" : "silver"
            }, 
            {
                "capacity" : "256G", 
                "style" : "gray"
            }
        ], 
        "popular_comment" : [
            {
                "content" : "Comment A content !", 
                "user_id" : 399.0, 
                "popularity_degree" : 89.0
            }, 
            {
                "content" : "Comment C content !", 
                "user_id" : 119.0, 
                "popularity_degree" : 108.0
            }, 
            {
                "content" : "Comment E content !", 
                "user_id" : 893.0, 
                "popularity_degree" : 103.0
            }
        ], 
        "keyword" : [
            "蘋果", 
            "iphone", 
            "apple"
        ], 
        "store" : "第三方渠道"
    }, 
    "ok" : 1.0
}

還有非常多的屬性可以去設置,我就不一一去說了,這裏貼出一下官方文檔的方法屬性配置表:

Field Type Description
query document Optional. The selection criteria for the modification. The query field employs the same query selectors as used in the db.collection.find() method. Although the query may match multiple documents, findAndModifywill only select one document to modify.
sort document Optional. Determines which document the operation modifies if the query selects multiple documents. findAndModify modifies the first document in the sort order specified by this argument.
remove boolean Must specify either the remove or the update field. Removes the document specified in the query field. Set this to true to remove the selected document . The default is false.
update document Must specify either the remove or the update field. Performs an update of the selected document. The update field employs the same update operators or field: value specifications to modify the selected document.
new boolean Optional. When true, returns the modified document rather than the original. The findAndModify method ignores the new option for remove operations. The default is false.
fields document Optional. A subset of fields to return. The fieldsdocument specifies an inclusion of a field with 1, as in: fields: { <field1>: 1, <field2>: 1, ...}. See projection.
upsert boolean

Optional. Used in conjuction with the update field.

When truefindAndModify() either:

  • Creates a new document if no documents match the query. For more details see upsert behavior.
  • Updates a single document that matches the query.

To avoid multiple upserts, ensure that the query fields are uniquely indexed.

Defaults to false.

bypassDocumentValidation boolean

Optional. Enables findAndModify to bypass document validation during the operation. This lets you update documents that do not meet the validation requirements.

New in version 3.2.

writeConcern document

Optional. A document expressing the write concern. Omit to use the default write concern.

New in version 3.2.

maxTimeMS integer Optional. Specifies a time limit in milliseconds for processing the operation.
findAndModify string The collection against which to run the command.
collation document

Optional.

Specifies the collation to use for the operation.

Collation allows users to specify language-specific rules for string comparison, such as rules for lettercase and accent marks.

The collation option has the following syntax:

collation: {
   locale: <string>,
   caseLevel: <boolean>,
   caseFirst: <string>,
   strength: <int>,
   numericOrdering: <boolean>,
   alternate: <string>,
   maxVariable: <string>,
   backwards: <boolean>
}

When specifying collation, the locale field is mandatory; all other collation fields are optional. For descriptions of the fields, see Collation Document.

If the collation is unspecified but the collection has a default collation (see db.createCollection()), the operation uses the collation specified for the collection.

If no collation is specified for the collection or for the operations, MongoDB uses the simple binary comparison used in prior versions for string comparisons.

You cannot specify multiple collations for an operation. For example, you cannot specify different collations per field, or if performing a find with a sort, you cannot use one collation for the find and another for the sort.

New in version 3.4.

Output

The findAndModify command returns a document with the following fields:

Field Type Description
value document Contains the command’s returned value. See value for details.
lastErrorObject document Contains information about updated documents. See lastErrorObject for details.
ok number Contains the command’s execution status. 1 on success, or 0 if an error occurred.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章