I started playing with MongoDB recently and I created a small database for learning purposes.
I used this to get an array with items
> var myitems = db.items.find({ $or: [ {"title": "item 1"}, {"title": "item 2"}, {"title": "item 3"}] })
> myitems
{ "_id" : ObjectId("570a841a8b71efa49d08fdda"), "title" : "item 1", "date" : ISODate("2016-04-10T16:49:30.242Z") }
{ "_id" : ObjectId("570a850c8b71efa49d08fddb"), "title" : "item 2", "date" : ISODate("2016-04-10T16:53:32.554Z") }
{ "_id" : ObjectId("570a85128b71efa49d08fddc"), "title" : "item 3", "date" : ISODate("2016-04-10T16:53:38.554Z") }
I also have a list object
{
"_id": int,
"total_items": int,
"items": [
"item_id": int
]
}
I want to insert my items array into list collection entries. Something like this:
> db.lists.insert({ "total_items": 5, "items": { $addToSet: myitems} })
Of course it didn't work. And that's what I'm looking for. A way to get the IDs from my items list and use them to insert a new entry to lists.
How can I do that?
You're using the insert command when you should be using the update command.
db.lists.updateOne({
_id: "abc"
}, {
$addToSet: {
items: episodes
}
})
Full docs here.
Related
I am having trouble understanding why an index is not able to cover a certain query, when my interpretation of documentation suggests it should... :)
The document I am referring to is: https://docs.mongodb.com/manual/core/index-multikey/
I am creating an index on a property which is part of an array of objects. The value indexed is present in other documents. The query looks up directly for the value of the property in the array. But when I look at the plan in the profiler, it is looking through the entire collection.
The structure of the document is as follows:
{
"userEmail": "string",
"basicInformation": {
"name" : "string"
},
"events": {
"live" : [
{"eventId": "id of event 1", // <--- field indexed : "events.live.eventId"
"date" : "date of event",
"duration": n},
{"eventId": "id of event 2",
"date" : "date of event",
"duration": n},
...
],
"onDemand" : [
{"eventId": "id of event 1", // <--- field indexed : "events.onDemand.eventId"
"date" : "date of event",
"duration": n},
{"eventId": "id of event 2",
"date" : "date of event",
"duration": n},
...
]
}
QUERY:
{
$facets: {
"liveUsers": [
{$match: {"events.live.eventId": "id of event 1"}},
{ $project: { .... }}
],
"onDemandUsers": [
{$match: {"events.live.eventId": "id of event 1"}},
{ $project: { .... }}
]
}
}
}
The plan does not seem to use the index and scans the collection. Currently the number of documents in the collection is over 63K, which leads to alerts. Can you help me understand how the indexes should be built or query restructured, so that we can avoid the full collection scan.
I have this kind of structure into a Mongo collection :
{
"_id": "12345678",
"Invoices": [
{
"_id": "123456789",
"Currency": "EUR",
"DueTotalAmountInvoice": 768.3699999999999,
"InvoiceDate": "2016-01-01 00:00:00.000",
"Items": [
{
"Item": 10,
"ProductCode": "ABC567",
"Quantity": 1
},
{
"Item": 20,
"ProductCode": "CDE987",
"Quantity": 1
}
]
},
{
"_id": "87654321",
"Currency": "EUR",
"DueTotalAmountInvoice": 768.3699999999999,
"InvoiceDate": "2016-01-01 00:00:00.000",
"Items": [
{
"Item": 30,
"ProductCode": "PLO987",
"Quantity": 1,
"Units": "KM3"
},
{
"Item": 40,
"ProductCode": "PLS567",
"Quantity": 1,
"DueTotalAmountInvoice": 768.3699999999999
}
]
}
]
}
So I have a first object storing several Invoices and each Invoice is storing several Items. An item is an embedded document.
So in relational modelisation :
A customer has 1 or several Invoice
An Invoice has 1 or several Item
I am facing an issue since I am trying to update a specific Item into a specific a specific Invoice. For example I want to change the quantity of the item 10 in Invoice 123456789.
How is it possible to do that in Mongodb ?
I tried :
Push statement but it doesn't seem to work for nested arrays
arrayFilters but it doesn't seem to work for embedded document in nested arrays (only simple value arrays).
Can you give me some advice about it ?
Thank you !
As per your problem description here:
For example I want to change the quantity of the item 10 in Invoice 123456789. I just changed the Quantity to 3. You can perform any operations here as you want. You just need to take note of how I used arrayFilters here.
Try this query:
db.collection.update(
{"_id" : "12345678"},
{$set:{"Invoices.$[element1].Items.$[element2].Quantity":3}},
{multi:true, arrayFilters:[ {"element1._id": "123456789"},{
"element2.Item": { $eq: 10 }} ]}
)
The above query successfully executed from mongo shell (Mongo 3.6.3). And I see this result:
/* 1 */
{
"_id" : "12345678",
"Invoices" : [
{
"_id" : "123456789",
"Currency" : "EUR",
"DueTotalAmountInvoice" : 768.37,
"InvoiceDate" : "2016-01-01 00:00:00.000",
"Items" : [
{
"Item" : 10,
"ProductCode" : "ABC567",
"Quantity" : 3.0
},
{
"Item" : 20,
"ProductCode" : "CDE987",
"Quantity" : 1
}
]
},
{
"_id" : "87654321",
"Currency" : "EUR",
"DueTotalAmountInvoice" : 768.37,
"InvoiceDate" : "2016-01-01 00:00:00.000",
"Items" : [
{
"Item" : 30,
"ProductCode" : "PLO987",
"Quantity" : 1,
"Units" : "KM3"
},
{
"Item" : 40,
"ProductCode" : "PLS567",
"Quantity" : 1,
"DueTotalAmountInvoice" : 768.37
}
]
}
]
}
Is that what you wanted?
Mongo Db has a way to get the specific array element by using its index. For example, you have an array and you need to get [your] index, then in mongo we use dot . but not braces [ ] !! And one thing is important either! - If you are getting the embedded value (in object or array) you must use " " for your way so if you are changing your value inside this must be like that:
yourModel.findOneAndUpdate(
{ _id: "12345678" },
{
$set: {
"Invoices.0.Items.0.Quantity": 10,
},
}
);
0 - is your element indexes in the array!
$set is the operator to set new value
10 - new value
Else you can go further, you can construct your way to the value with the variable indexes. Use string template
yourModel.findOneAndUpdate(
{ _id: "12345678" },
{
$set: {
[`Invoices.${invoiceIndex}.Items.${itemIndex}.Quantity`]:newValue ,
},
}
);
it is the same but you can paste variable indexes
Hi I am developing MeteorJS app, I am stuck at updating a sub sub array element.
It is a poll application and I have the following database structure:
Under each question there are options and when a user clicks a button of an option, I want to increment that options votes by one and every user should have one vote right for each question.
From the button, I am passing name and questionId data in order to find the right option to increment vote. I should find the specific question with the questionId and then the specific array with the name under the Options.
Where I am stuck at is I can't find it.
Please help, thanks
Collection Name: Polls
Each Poll has the following structure:
{
"_id" : "uJtBt8mM2pbTYfwND",
"createdAt" : ISODate("2017-04-03T22:40:14.678Z"),
"pollName" : "First Poll",
"entryOwner" : "gdAHxDrxFuTvYiFt8",
"question" : [
{
"name" : "Question number 1",
"questionId" : "xgYQxGxpwBXaQpjXN",
"options" : [
{
"name" : "John",
"votes" : 0
},
{
"name" : "Adam",
"votes" : 0
},
{
"name" : "Robert",
"votes" : 0
}
]
},
{
"name" : "Question number 2",
"questionId" : "zviwYHHsaATBdG6Jw",
"options" : [
{
"name" : "John",
"votes" : 0
},
{
"name" : "Adam",
"votes" : 0
},
{
"name" : "Robert",
"votes" : 0
}
]
}
],
}
You can use $and which performs a logical AND operation on an array of two or more expressions.
{ $and: [ { <expression1> }, { <expression2> } , ... , { <expressionN> } ] }
The first expression here would be to get the question with questionId.
'question.questionId': "xgYQxGxpwBXaQpjXN"
And second expression to specify the object with matching name in options array.
To find the object with the name in options array, you can use $elemMatch which allows to specify queries.
{ <field>: { $elemMatch: { <query1>, <query2>, ... } } }
To get the object in options array having name as "John".
'question.options': {
$elemMatch: {
name: "John"
}
}
And finally, use $inc to increase the votes (here by 1).
It will get the first matching element (with $).
'question.options.$.votes': 1
Here's the full code:
db.Polls.update({
$and: [{
'question.questionId': "xgYQxGxpwBXaQpjXN"
},
{
'question.options': {
$elemMatch: {
name: "John"
}
}
}
]
}, {
$inc: {
'question.options.$.votes': 1
}
})
I have a DB with a set of fields f1,f2,f3, where f3 is an array. I would like to do an update operation such as this
db.collection.update ({f1:1},{$push:{f3:{$each:[f2's value],$slice:-2}}})
I searched on the internet and was not abole to find anything. I am not sure if this is even possible. Any help would be much appreciated.
EXAMPLE:
This is my set of documents:
doc1 = { name: "User1", score: "Good", History of scores: ["Good", "Bad", "Average", "Bad"] }
doc2 = { name: "User2", score: "Bad", History of scores: ["Good", "Average", "Average", "Bad"] }
doc3 = { name: "User3", score: "Good", History of scores: ["Good", "Good", "Average", "Good"] }
Now suppose we have to insert the corresponding data:
{name : "User1", score: "Good"}
I would like the document to update user1's history of scores so that doc1 becomes as follows:
doc1 = { name: "User1", score: "Good", History of scores: ["Bad", "Average", "Bad", "Good"] }
another of the same update should change doc1 to:
doc1 = { name: "User1", score: "Good", History of scores: ["Average", "Bad", "Good", "Good"] }
I hope now my question has become clearer. Thanks.
Try this:
> db.c.find()
{ "_id" : ObjectId("51c156d25a334e9347b576a7"), "name" : "User1", "score" : "Good", "scores" : [ "Good", "Bad", "Average", "Bad" ] }
> db.c.update({}, {$push: {scores:{$each:['111', '222'], '$slice': -4}}})
> db.c.find()
{ "_id" : ObjectId("51c156d25a334e9347b576a7"), "name" : "User1", "score" : "Good", "scores" : [ "Average", "Bad", "111", "222" ] }
btw, I there is a problem with this kind of updates: if new object is grater then previous in size, it cause moving this object to another location on disk(e.g. you pushed "Average" and popped "Bad"). Updates "in-place" is faster, you can preallocate space for objects on first insert, like so:
> db.c.insert({ "_id" : ObjectId("51c156d25a334e9347b576a7"), "name" : "<big_tmp_string>", "score" : "<big_tmp_string>", "scores" : [ "<big_tmp_string>", "<big_tmp_string>", "<big_tmp_string>", "<big_tmp_string>" ] })
> db.c.update({ "_id" : ObjectId("51c156d25a334e9347b576a7")}, {<your_real_obj>}
Now that update commands can contain pipelines as of MongoDB 4.2, something like this is possible.
db.collection.updateOne({ f1: 1 }, [{
$set: {
historyOfScores: {
$concatArrays: [
"$historyOfScores",
["$score"]
]
}
}
}, {
$set: {
score: 'Good'
}
}]
I have the following schema, blog collection & friendscoll as below
blogpostcollection
{
"_id" : ObjectId("4fff0bf18bf0d19c4f1a5826"),
"author" : "joe",
"text" : "Here is the text...",
"userid" : 0
}
{
"_id" : ObjectId("4fff0bf18bf0d19c4f1a5827"),
"author" : "blake",
"text" : "Here is the text...",
"userid" : 1
}
{
"_id" : ObjectId("4fff0bf18bf0d19c4f1a5828"),
"author" : "joe",
"text" : "Here is the text...",
"userid" : 2
}
myfriendscoll
{
"myid": 999,
"data": [
{
"uid": 1,
"name": "Raul"
},
{
"uid": 3,
"name": "John K"
} ]
}
I want to find all documents in blogpostcollection, where the userid exists as uid, in the myfriendscoll collection.
So in effect, something like..
var user = db.myfriendscoll.findOne({"myid" : 999}, {"data.uid": 1});
db.blogpostcollection.find( {"userid" : {$in : user.data.uid}});
This doesn't work, but is there a way to get it to work?...Thanks!
If you are using development version 2.1 or when you move to 2.2 once it's released you can use the aggregation framework to get the format you want back from the first query:
var ret=db.myfriendscoll.aggregate([
{$match:{"myid" : 999}},
{$project:{_id:0,uid:"$data.uid"}}
]);
var uids=ret.result[0].uid;
db.blogpostcollection.find({userid:{$in:uids}})
You'll need to extract the actual uid values into an array to use with $in. Try this:
var user = db.myfriendscoll.findOne({"myid" : 999}, {"data.uid": 1});
var uids = user.data.map(function(v){ return v.uid });
db.blogpostcollection.find( {"userid" : {$in : uids}});