MongoDB update nested array elements - arrays

I have the following structure:
{
id: "1",
invoices: [{ id: "1", balance: 1},{ id: "2", balance: 1}]
},
{
id: "2",
invoices: [{ id: "3", balance: 1},{ id: "4", balance: 1}]
}
I'm getting a list of invoices IDs that i shouldn't update, the rest i need to update the balance to 0.
I'm pretty new to MongoDB and managing to find a way to do it.

Let say you want to update all invoices of id 1 except invoice.id 2 try this one:
db.collection.update(
{ id: "1", "invoices.id": {$ne: 2} },
{
$set: {
"invoices.$[]": { balance: 0 }
}
}
)

First of all, you forgot the quotes around the field names. Your documents should be like this:
{
"id": "1",
"invoices": [{
"id": "1",
"balance": 1
}, {
"id": "2",
"balance": 1
}]
}
I have limited experience with MongoDB, as I learnt it this semester at University. However, here is my solution:
db.collection.update(
{ id: "1" },
{
$set: {
"invoices.0": { id: "1", balance: 0 }
}
}
)
What does this solution do?
It takes the document with id 1. That is your first document.
The $set operator replaces the value of a field with the specified value. (straight out from the MongoDB manual - MongoDB Manual - $set operator).
"invoices.0" takes the first invoice from the invoices array and then it updates the balance to 100.
Replace the word collection from db.collection with your collection name.
Try and see if it works. If not, I'd like someone with more experience to correct me.
LE: Now it works, try and see.

Related

MongoDB - Pipeline $lookup with $group losing fields

I only have 2 years exp with SQL databases and 0 with NoSQL database. I am trying to write a pipeline using MongoDB Compass aggregate pipeline tool that performs a lookup, group, sum, and sort. I am using MongoDB compass to try and accomplish this. Also, please share any resources that make learning this easier, I've not had much like finding good and easy-to-understand examples online with using the compass to accomplish these tasks. Thank you.
An example question I am trying to solve is:
What customer placed the highest number of orders?
Example Data is:
Customer Collection:
[
{ "_id": { "$oid": "6276ba2dd1dfd6f5bf4b4f53" },
"Id": "1",
"FirstName": "Maria",
"LastName": "Anders",
"City": "Berlin",
"Country": "Germany",
"Phone": "030-0074321"},
{ "_id": { "$oid": "6276ba2dd1dfd6f5bf4b4f54" },
"Id": "2",
"FirstName": "Ana",
"LastName": "Trujillo",
"City": "México D.F.",
"Country": "Mexico",
"Phone": "(5) 555-4729" }
]
Order Collection:
[
{ "_id": { "$oid": "6276ba9dd1dfd6f5bf4b501f" },
"Id": "1",
"OrderDate": "2012-07-04 00:00:00.000",
"OrderNumber": "542378",
"CustomerId": "85",
"TotalAmount": "440.00" },
{ "_id": { "$oid": "6276ba9dd1dfd6f5bf4b5020" },
"Id": "2",
"OrderDate": "2012-07-05 00:00:00.000",
"OrderNumber": "542379",
"CustomerId": "79",
"TotalAmount": "1863.40" }
]
I have spent all day looking at YouTube videos and MongoDB documentation but I am failing to comprehend a few things. One, at the time I do a $group function I lose all the fields not associated with the group and I would like to keep a few fields. I would like to have it returned the name of the customer with the highest order.
The pipeline I was using that gets me part of the way is the following:
[{
$lookup: {
from: 'Customer',
localField: 'CustomerId',
foreignField: 'Id',
as: 'CustomerInfo'
}}, {
$project: {
CustomerId: 1,
CustomerInfo: 1
}}, {
$group: {
_id: '$CustomerInfo.Id',
CustomerOrderNumber: {
$sum: 1
}
}}, {
$sort: {
CustomerOrderNumber: -1
}}]
Example data this returns in order:
Apologies for the bad formatting, still trying to get the hang of posting questions that are easy to understand and useful.
In $group stage, it only returns documents with _id and CustomerOrderNumber fields, so CustomerInfo field was missing.
$lookup
$project - From 1st stage, CustomerInfo returns as an array, hence getting the first document as a document field instead of an array field.
$group - Group by CustomerId, sum the documents as CustomerOrderNumber, and take the first document as CustomerInfo.
$project - Decorate the output documents.
$setWindowsFields - With $denseRank to rank the document position by CustomerOrderNumber (DESC). If there are documents with same CustomerOrderNumber, the ranking will treat them as same rank/position.
$match - Select documents with denseRankHighestOrder is 1 (highest).
db.Order.aggregate([
{
$lookup: {
from: "Customer",
localField: "CustomerId",
foreignField: "Id",
as: "CustomerInfo"
}
},
{
$project: {
CustomerId: 1,
CustomerInfo: {
$first: "$CustomerInfo"
}
}
},
{
$group: {
_id: "$CustomerInfo.Id",
CustomerOrderNumber: {
$sum: 1
},
CustomerInfo: {
$first: "$CustomerInfo"
}
}
},
{
$project: {
_id: 0,
CustomerId: "$_id",
CustomerOrderNumber: 1,
CustomerName: {
$concat: [
"$CustomerInfo.FirstName",
" ",
"$CustomerInfo.LastName"
]
}
}
},
{
$setWindowFields: {
sortBy: {
CustomerOrderNumber: -1
},
output: {
denseRankHighestOrder: {
$denseRank: {}
}
}
}
},
{
$match: {
denseRankHighestOrder: 1
}
}
])
Sample Mongo Playground
Note:
$sort stage able to sort the document by CustomerOrderNumber. But if you try to limit the documents such as "SELECT TOP n", the output result may be incorrect when there are multiple documents with the same CustomerOrderNumber/rank.
Example: SELECT TOP 1 Customer who has the highest CustomerOrderNumber but there are 3 customers who have the highest CustomerOrderNumber.

MongoDB check if key value exists and update only some fields

Let's say I have a MongoDB collection "people" that has the form
[
{
_id: [OBJECT_ID_1],
name: "Paul",
hobby: "rowing",
fixed: 1
},
{
_id: [OBJECT_ID_2],
name: "Selena",
hobby: "drawing",
fixed: 2
},
{
_id: [OBJECT_ID_3],
name: "Emily",
hobby: "jogging",
fixed: 3
}
]
And new data to be inserted of the form
var data = [
{
name: "Paul", // name exists, so keep "fixed" at 1
hobby: "archery",
fixed: 4
},
{
name: "Peter",
hobby: "knitting",
fixed: 5
}
]
I would like to insert/update the collection with the new data. However, if a document with the same "name" already exists, I do not want to update "fixed". The result after inserting the above data should be
[
{
_id: [OBJECT_ID_1],
name: "Paul",
hobby: "archery", // updated
fixed: 1 // not updated, because name existed
},
{
_id: [OBJECT_ID_2],
name: "Selena",
hobby: "drawing",
fixed: 2
},
{
_id: [OBJECT_ID_3],
name: "Emily",
hobby: "jogging",
fixed: 3
},
{ // newly inserted document
_id: [OBJECT_ID_4],
name: "Peter",
hobby: "knitting",
fixed: 5
}
]
The data includes a large number of documents, so I would like to achieve this in one query if possible. What would be the best way to accomplish this? Many thanks!
bulkWrite with updateOne's with $upsert:true seems to work best for you...
bulkWrite not perform a Find operation.
Its (in my case) is necessary to control if it will create a another document.
In my case I just use find check before insert/create method.
if (collection.find({"descr":descr}).limit(1).length === 1) {
//...create method
console.log('Exists')
}

How to update multiple objects in a single collection of mongodb?

I have one collection in mongodb as given below -
[
{
id: "1",
itemName: "pen",
quantity: 10
},
{
id: "2",
itemName: "notebook",
quantity: 20
},
{
id: "3",
itemName: "book",
quantity: 30
}
]
I have to update this collection in one go. From UI I am getting the request array as -
[
{
"id": "1",
"quantity": 12
},
{
"id":"2",
"quantity": 13
}
]
I need to update the corresponding objects. I can run it in a for loop and using
db.collection.update()
I can update it. But is there any way to pass the whole array to update the corresponding objects in one go?

How to check before updating an array element in MongoDB/NodeJS

In my sample document, I have a campaign document that contains the _id of the document and an importData array. importData is an array of objects containing a unique date and source value.
My goal is to have an object updated with a unique date/source pair. I would like to have the new object replace any matching object. In the example below, Fred may have originally donated a TV, but I want my application to update the object to reflect he donated both a TV and a radio.
// Events (sample document)
{
"_id" : "Junky Joe's Jubilee",
"importData" : [
{
"date": "2015-05-31",
"source": "Fred",
"items": [
{item: "TV", value: 20.00},
{item: "radio", value: 5.34}
]
},
{
"date": "2015-05-31",
"source": "Mary",
"items": [
{item: "Dresser", value: 225.00}
]
}
]
}
My original thought was to do something like the code below, but not only am I updating importData with Fred's donations, I'm also blowing away anything else in the importData array:
var collection = db.collection("events");
collection.update(
{_id: "Junky Joe's Jubilee",
importData: {
date: "2015-05-31",
source: 'Fred'
},
}, // See if we can find a campaign object with this name
{
$set:
{"importData":
{
date: "2015-05-31",
source: 'Fred',
items: [
{item: "TV", value: 20.00},
{item: "radio", value: 5.34}
]
}
}
},
{upsert: true}); // Create a document if one does not exist for this campaign
When I tried pushing (instead of $set), I was getting multiple entries for the date/source combos (e.g. Fred would appear to have donated two items multiple times on "2015-05-31").
How would I go about doing that with the MongoDB native driver and NodeJS?
Try this
var collection = db.collection("events");
collection.update(
{_id: "Junky Joe's Jubilee",
importData: {
date: "2015-05-31",
source: 'Fred'
},
}, // See if we can find a campaign object with this name
{
$set:
{"importData.$":
{
date: "2015-05-31",
source: 'Fred',
items: [
{item: "TV", value: 20.00},
{item: "radio", value: 5.34}
]
}
}
},
{upsert: true}); // Create a document if one does not exist for this campaign
According to the documentation under Array update operators this should only modify the first element in the array, which matches the query.

Update embedded mongoose document in array

Lets say that I have the following document in the books collection:
{
_id:0 ,
item: "TBD",
stock: 0,
info: { publisher: "1111", pages: 430 },
tags: [ "technology", "computer" ],
ratings: [ { _id:id1, by: "ijk", rating: 4 }, {_id:id2 by: "lmn", rating: 5 } ],
reorder: false
}
I would like to update the value of ratings[k].rating and all I know is the id of the collection and the _id of the objects existing in the array ratings.
The tutorial of mongoDB has the following example that uses the position of the object inside the array but I suppose that if the update can only be done by knowing the position, this means that I firstly have to find the position and then proceed with the update? Can I do the update with only one call and if so how I can do that?
db.books.update(
{ _id: 1 },
{
$inc: { stock: 5 },
$set: {
item: "ABC123",
"info.publisher": "2222",
tags: [ "software" ],
"ratings.1": { by: "xyz", rating: 3 }
}
}
)
Sorry for late answer; I think this is what you want to do with mongoose.
Books.findOneAndUpdate({
_id: 1,
'ratings._id': id1
},
{
$set: {
'ratings.$.rating' : 3
}
}, function(err, book){
// Response
});
Positional operator may help you:
db.books.update(
// find book by `book_id` with `rating_id` specified
{ "_id": book_id, "ratings._id": rating_id },
// set new `value` for that rating
{ $set: { 'ratings.$.rating': value }}
);
$ will save position of matched document.

Resources