I just wanted to know how to add to a array in a mongodb document because I only know how to reassign the whole array in the document.
As reported in MongoDB docs, if you have the following document in the students
collection:
{
"_id" : 5,
"quizzes" : [
{ "wk": 1, "score" : 10 },
{ "wk": 2, "score" : 8 },
{ "wk": 3, "score" : 5 },
{ "wk": 4, "score" : 6 }
]
}
you can add the following documents to the quizzes array with the following command:
db.students.update(
{ _id: 5 },
{
$push: {
quizzes: {
$each: [ { wk: 5, score: 8 }, { wk: 6, score: 7 }, { wk: 7, score: 6 } ]
}
}
}
)
Related
My Documents are as follows.
{
"order_id" : "1",
"payment_status" : false,
"items" : [
{
"item_id" : 1,
"payment_status" : false,
},
{
"item_id" : 2,
"payment_status" : false,
},
{
"item_id" : 3,
"payment_status" : false,
},
]
}
I need to update the fields payment_status for {"order_id":1} and {"item_id" : 1} and {"item_id" : 3}. Also, I need to update the same in bulk for matching conditions. Is this possible in mongoose?
You want to be using $arrayFilters like so:
db.collection.updateMany({
"order_id": "1"
},
{
"$set": {
"items.$[item].payment_status": true
}
},
{
arrayFilters: [
{
"item.item_id": {
$in: [
1,
3
]
}
}
]
})
Mongo Playground
In the application im building I have two updates that I want to do in the same query. I want to find the subdocument with the matching task_id and update its priority. In the same call I want to increment all the subdocuments with a priority higher than 3. Is it possible to combine these two in the same query?
const project = await Project.updateOne(
// first search
{ _id: req.params.project_id },
{ $set: {'tasks.$[element].priority': req.body.priority, 'tasks.$[element].state': req.body.state }},
{ arrayFilters: [{ 'element._id': req.params.task_id }] }
// second search
{ _id: req.params.project_id },
{ $inc: {'tasks.$[element].priority': 1 }},
{ arrayFilters: [{ 'element.priority': { $gt: 3 } }] }
);
you must be used different identifiers for your arrayFilters. your identifier is element. your code must be like that:
const project = await Project.updateOne(
// first search
{_id: req.params.project_id},
{
$set: {'tasks.$[elementA].priority': req.body.priority, 'tasks.$[elementA].state': req.body.state},
$inc: {'tasks.$[elementB].priority': 1}
},
{
arrayFilters: [
{'elementA._id': req.params.task_id},
{'elementB.priority': {$gt: 3}}
]
},
)
NOTE: The identifier must begin with a lowercase letter and contain only alphanumeric characters (from MongoDB official website, link)
You can do it simultaneously by using two arrayFilters. Consider the below:
Current collection:
{
"_id" : 1,
"array1" : [
{
"k1" : 1,
"v1" : 100
},
{
"k1" : 2,
"v1" : 15
},
{
"k1" : 1,
"v1" : 100
}
],
"array2" : [
{
"k2" : 1,
"v2" : 10
},
{
"k2" : 2,
"v2" : 1000
},
{
"k2" : 1,
"v2" : 20
}
]
}
Query:
db.collection.update(
{ _id: 1 },
{ $set:
{
'array1.$[elem1].v1': 100,
'array2.$[elem2].v2': 1000
}
},
{ arrayFilters:
[
{'elem1.k1':1},
{'elem2.k2': 2}
],
multi: true
}
)
As you can see that, I have created two filtered positional operator (elem1 and elem2), with the help of arrayFilters option. I can used this to perform my updates.
Result:
{
"_id" : 1,
"array1" : [
{
"k1" : 1,
"v1" : 100
},
{
"k1" : 2,
"v1" : 15
},
{
"k1" : 1,
"v1" : 100
}
],
"array2" : [
{
"k2" : 1,
"v2" : 10
},
{
"k2" : 2,
"v2" : 1000
},
{
"k2" : 1,
"v2" : 20
}
]
}
You can see in the above updated collection that the k1 field in array1 with value 1, it's v1 field have been updated to 100 and the k2 field in array2 with value 2, it's v2 field have been updated to 100.
So in your case you need to do something like below:
updateOne(
{ _id: req.params.project_id},
{
$set: {
'tasks.$[elem1].priority': req.body.priority,
'tasks.$[elem1].state': req.body.state
},
$inc: {
'tasks.$[elem2].priority': 1
}
},
{
arrayFilters: [
{ 'elem1._id': req.params.task_id },
{ 'elem2.priority':
{ $gt: 3 }
}
]
}
)
I hope it's helpful.
I've problem to find remaining seats of particular restaurant in a particular Date range
Let me define my problem. Suppose I've a collection it called booking
In this have a following fields:
Booking ID
User ID
Restaurant ID
Booking Start Date (date saved in Timestamp)
Booking End Date (date saved in Timestamp)
Booked Seat
If there are 50 seats in restaurant. And I want to check available seats of a particular date range for example the date range is 6-12(This range in timestamp)"(Start Date) - (End Date)"
How to calculate the total remaining seats of restaurant.
If anyone have any idea please let me know.
Thanks in advance
Code is here
const where = {};
where.restaurant_id = 126;
where.user_id = 1;
where.start_date = { $gte: 6 };
where.end_date = { $gte: 12 };
const remainingSeats = getSeatsAvailability(where);
function getSeatsAvailability(where) {
return new Promise((resolve, reject) => {
bookingModel.find(where, (err, details) => {
if (err) { reject(err); } else { resolve(details); }
});
});
}
So, What I have Visualized is the below diagram, so there are basically four cases to be covered:
x y
a-----------|---------b |
a-|------------|---b
| a-------b |
| a-----|--------b
So the formulae are:
1. a<x, a<y | b>x, b<y
2. a<x, a<y | b>x, b>y
3. a>x, a<y | b>x, b>y
4. a>x, a<y | b<y, b>x
I am pasting an ROUGH example, take it as a hint to solve the problem at hand:
db.collection.aggregate([
{
$match: {
$or: [
{
a: {
$lte: 3,
$lte: 9
},
$or: [
{
b: {
$gte: 3,
$lte: 9
}
},
{
b: {
$gte: 3,
$gte: 9
}
}
]
},
{
a: {
$gte: 3,
$lte: 9
},
$or: [
{
b: {
$gte: 3,
$gte: 9
}
},
{
b: {
$gte: 3,
$lte: 9
}
}
]
}
],
r: 1
}
},
{
$group: {
_id: null,
sum_booked: {
$sum: "$c"
}
}
},
{
$project: {
seats_left: {
$subtract: [
50,
"$sum_booked"
]
}
}
}
])
The example dataset that I have tested on:
[
{
a: 1,
b: 10,
c: 10,
r: 1,
},
{
a: 2,
b: 5,
c: 15,
r: 1
},
{
a: 5,
b: 10,
c: 10,
r: 1
},
{
a: 7,
b: 15,
c: 15,
r: 12 <<<<========/////DIFF REST ID
}
]
Output:
[
{
"_id": null,
"seats_left": 15
}
]
db.booking.aggregate([{
$match: {
BookingStartDate: { $lte: date_range_end },
BookingEndDate: { $gte: date_range_start },
RestaurantID: restaurant_id
}
},
{
$group: {
_id: '$RestaurantID',
TotalBookedSeat: { $sum: '$BookedSeat' }
}
},
{
$project: {
LeftSeat: { $subtract: [50, '$TotalBookedSeat'] },
_id: 0
}
}
])
{ "_id" : ObjectId("5d8479f6902a52448029a6d0"), "bid" : 1, "uid" : 1, "rid" : 126, "bsd" : 1, "bed" : 10, "bs" : 10 }
{ "_id" : ObjectId("5d847a11902a52448029a6d1"), "bid" : 2, "uid" : 1, "rid" : 126, "bsd" : 2, "bed" : 5, "bs" : 15 }
{ "_id" : ObjectId("5d847a24902a52448029a6d2"), "bid" : 3, "uid" : 1, "rid" : 126, "bsd" : 5, "bed" : 10, "bs" : 10 }
{ "_id" : ObjectId("5d847a34902a52448029a6d3"), "bid" : 4, "uid" : 1, "rid" : 126, "bsd" : 7, "bed" : 15, "bs" : 15 }
db.booking.aggregate([{ $match: { bsd: { $lte: 12 }, bed: { $gte: 6 }, rid: 126 } }, { $group: { _id: '$rid', total_seat_sold: { $sum: '$bs' } } }])
The result of the above is 35, then 15 seats left.
This time it will works I think.
I would like to group records by day for certain period. I have tried so far using this code added into the aggregate function:
{
$group : {
_id : { day: { $dayOfMonth: "$timestamp" }},
count: { $sum: 1 }
}
}
And this is how a document looks like:
{
"_id" : ObjectId("ec9cddd50e08a84cd3f4cccb"),
"orgid" : "5c48500d84430a3a4b828e85",
"timestamp" : ISODate("2019-03-28T14:00:00.000Z"),
"apiid" : {
"zxczxczxczxczxc" : {
"errortotal" : 6,
"hits" : 6,
"humanidentifier" : "Feedback",
"identifier" : "663cfc345e42401c6443cfd635301f8f",
"lasttime" : ISODate("2019-03-28T14:58:07.355Z"),
"success" : 0,
"totalrequesttime" : 0.0,
"requesttime" : 0.0
}
},
"apikeys" : {
"00000000" : {
"errortotal" : 3,
"hits" : 3,
"humanidentifier" : "",
"identifier" : "00000000",
"lasttime" : ISODate("2019-03-28T14:55:10.438Z"),
"success" : 0,
"totalrequesttime" : 0.0,
"requesttime" : 0.0
},
"cae81afc" : {
"errortotal" : 3,
"hits" : 3,
"humanidentifier" : "EE5RqcXMTqcWEx8nZv3vRATLspK2",
"identifier" : "cbe81afc",
"lasttime" : ISODate("2019-03-28T14:58:07.355Z"),
"success" : 0,
"totalrequesttime" : 0.0,
"requesttime" : 0.0
}
}
Any idea how can I achieve this?
Result I get is: [ { _id: { day: null }, count: 3 } ], it seems wrong for me since I have two documents with the same date and another document with different timestamp
UPDATE:
I also have this inside aggregate fuction:
// Project things as a key/value array, along with the original doc
{
$project: {
array: {$objectToArray: '$apikeys'},
doc: '$$ROOT',
}
},
// Match the docs with a field value of 'x'
{$match: {'array.v.humanidentifier': {$in: trialCustomerUsers}}},
If I comment this part it will work fine the grouping, but the thing is I would also do some where statement in cases where I also dont know what woudl be the key, that's why I had to add this piece of code
Just accumulate the records in a new field with the $push operator
{
$group : {
_id : { day: { $dayOfMonth: "$timestamp" }},
records: { $push: "$$ROOT" }
}
}
You have $projected your all the root document in the doc field using $$ROOT. Now your aggregation should be as followed
db.collection.aggregate([
{ "$project": {
"array": { "$objectToArray": "$apikeys" },
"doc": "$$ROOT"
}},
{ "$match": { "array.v.humanidentifier": { "$in": trialCustomerUsers }}},
{ "$group" : {
"_id" : { "day": { "$dayOfMonth": "$doc.timestamp" }},
"count": { "$sum": 1 }
}}
])
Change this line
_id : { day: { $dayOfMonth: "$timestamp" }}
to
_id : { day: { $day: "$timestamp" } }
or you can do something like this
$group : {
_id : null,
day: '$timestamp',
count: { $sum: 1 }
}
For a collection with 200k documents like the following:
{
name: Mario,
profession: plumber,
level: 8,
},
{
name: Luigi,
profession: plumber,
level: 5,
},
{
name: Walter,
profession: cook,
level: 10,
},
{
name: Jesse,
profession: cook,
level: 3,
}
What would be an efficient query to get only a single document per profession, with or without sorting by level?
To expand on #felix answer, you can do this easily with a MongoDB group operation. For example, in the MongoDB shell:
db.yourCollection.aggregate([
{ $group: { _id: '$profession', doc: { $first: '$$ROOT' } } }
])
will give you results like:
{
_id: 'plumber',
doc: {
name: 'Mario',
profession: 'plumber',
level: 8
}
},
{
_id: 'cook',
doc: {
...
}
},
...
If more fine-grained control is required, you can filter the results before or after the grouping with $match. Any MongoDB adaptor for Node should give you access to this method.
use the $group and aggregate() function to get your expected result
Query:
db.getCollection('agg').aggregate([
{ $group: { _id: '$profession', doc: { $first: '$$ROOT' }, count: { $sum: 1 } } }
])
Output:
/* 1 */
{
"_id" : "cook",
"doc" : {
"_id" : ObjectId("59157ace263af55a862b78dc"),
"name" : "Walter",
"profession" : "cook",
"level" : 10.0
},
"count" : 2.0
}
/* 2 */
{
"_id" : "plumber",
"doc" : {
"_id" : ObjectId("59157ace263af55a862b78da"),
"name" : "Mario",
"profession" : "plumber",
"level" : 8.0
},
"count" : 2.0
}