How do I findOndAndUpdate an item of an array? - mongodb

This is the update statement:
const cart = await Cart.findOneAndUpdate({
userId: userId,
'items._id': itemId,
'items.product': productId,
'items.size': size,
'items.color': color,
}, {
$set: {
'items.$.quantity': quantity
}
}, {
new: true
}).populate({
path: 'items.product',
model: 'Product'
})
This is the new quantity to be applied to the array:
newQuantity {
itemId: '625065c99edbfad52ac3afce',
productId: '6205a4565c0caba6fb39cd5d',
size: '3',
quantity: '3',
color: 'blue2'
}
This is the data in the database, the first item of the array is updated with the new quantity rather than the second item that meets the query criteria shown above.
{
"_id": {
"$oid": "623a1f208ea52c030dc331a5"
},
"userId": {
"$oid": "623a1f208ea52c030dc331a3"
},
"items": [
{
"quantity": 2,
"product": {
"$oid": "6205a4565c0caba6fb39cd5d"
},
"size": "3",
"color": "blue1",
"prodImage": "mom-jeans-3.1.png",
"_id": {
"$oid": "625065c69edbfad52ac3afc2"
}
},
{
"quantity": 1,
"product": {
"$oid": "6205a4565c0caba6fb39cd5d"
},
"size": "3",
"color": "blue2",
"prodImage": "mom-jeans-5.1.png",
"_id": {
"$oid": "625065c99edbfad52ac3afce"
}
},
{
"quantity": 1,
"product": {
"$oid": "6205a4565c0caba6fb39cd5d"
},
"size": "3",
"color": "blue3",
"prodImage": "mom-jeans-4.1.png",
"_id": {
"$oid": "625065cc9edbfad52ac3afdc"
}
}
],
"__v": 0
}

You can use array filters to get the desired output:
The userId into find query will filter across all documents, and the array filter will find the object in the array which match your condition.
db.collection.update({
"userId": userId
},
{
"$set": {
"items.$[item].quantity": quantity
}
},
{
"arrayFilters": [
{
"item._id": itemId,
"item.product": productId,
"item.size": size,
"item.color": color
}
]
})
Example here

'$' operator only update the first element found.
You should use $[] operator:
const cart = await Cart.findOneAndUpdate({
userId: userId,
'items._id': itemId,
'items.product': productId,
'items.size': size,
'items.color': color,
}, {
$set: {
'items.$[].quantity': quantity
}
}, {
new: true
}).populate({
path: 'items.product',
model: 'Product'
})
Try it.

Related

MongoDB, return selected fields from document with aggregation operations

With given query I also want to return productId.
I have collection comments that contains documents with data about productId and comments for given product
Example document in this collection:
{
"_id": {
"$oid": "635ee64f55460d1796447662"
},
"productId": "63413800d36ed477adc763d0",
"__v": 0,
"comments": [
{
"userId": "",
"userName": "test",
"date": "2022.12.18.21.51.36",
"confirmed": false,
"likes": {
"up": 0,
"down": 0
},
"content": {
"rating": 6,
"description": "testtesttest"
},
"image": {
"added": false,
"images": []
},
"_id": {
"$oid": "639f7d58b6206a863c4a7aba"
},
"usersWhoLiked": []
},
{
"userId": "",
"userName": "test",
"date": "2022.12.18.21.52.19",
"confirmed": false,
"likes": {
"up": 0,
"down": 0
},
"content": {
"rating": 6,
"description": "testtesttest"
},
"image": {
"added": true,
"images": [
"comments/63413800d36ed477adc763d0/639f7d83b6206a863c4a7ad6/dell.jpg"
]
},
"_id": {
"$oid": "639f7d83b6206a863c4a7ad6"
},
"usersWhoLiked": []
}
]
}
My exmaple query:
db.comments.aggregate([{$match: {"comments._id": {$in: [ObjectId('630b7868f51e10876223b4aa'), ObjectId('630bd277f919a9e9c0e7a559')]}}},
{$project: {comment: {$filter: {input: "$comments", as: "comment", cond: {$in: ["$$comment._id", [ObjectId("630b7868f51e10876223b4aa"), ObjectId("630bd277f919a9e9c0e7a559")]]}}}}}])
With this query I get the result :
{ _id: ObjectId("630b7868f51e10876223b4a6"),
comment:
[ { userId: '62f29c2c324f4778dff443f6',
userName: 'User',
date: '2022.08.19',
confirmed: false,
likes: { up: 3, down: 0 },
content: { rating: 4, description: 'Super laptop <3' },
_id: ObjectId("630b7868f51e10876223b4aa"),
usersWhoLiked:
[ { userId: '62f29c2c324f4778dff443f6',
likeUp: true,
_id: ObjectId("630d2b0494370efb37107983") },
{ userId: '6322434f2b5bbac87f0e7aba',
likeUp: true,
_id: ObjectId("632243702b5bbac87f0e7afa") },
{ userId: '62f2991e324f4778dff443d4',
likeUp: true,
_id: ObjectId("63af4d77c8991b74d6986995") } ] } ] }
{ _id: ObjectId("630bd277f919a9e9c0e7a555"),
comment:
[ { userId: '62f29c2c324f4778dff443f6',
userName: 'User',
date: '2022.08.28',
confirmed: false,
likes: { up: 1, down: 1 },
content:
{ rating: 6,
description: 'Laptop posiada przyjemna klawiature, nie grzeje siÄ™. Do codziennego grania wystarczy.' },
_id: ObjectId("630bd277f919a9e9c0e7a559"),
usersWhoLiked:
[ { userId: '62f29c2c324f4778dff443f6',
likeUp: true,
_id: ObjectId("630d2dfc94370efb37107991") },
{ userId: '62fa2549f029348f75bc9c81',
likeUp: false,
_id: ObjectId("631241fe755c641525dc9cfa") } ] } ] }
As you see in the result, the productId is missing.
I was trying to rebuild query with #group operator but still with out effect...
So my questsion is:
How shall I rewrite that query to get the same result but with productId in it for each returned comment
This is how $project works, if a field is not specified will not be output.
So just add productId: 1 into the $project stage and it will be shown.
Check this example.

unable to update a subdocuments in an array

im trying to update a value for uid in solts.slots but nothing works.
i want to iterate two level of array to modify the document
{
"_id": {
"$oid": "638455dee12f0122c9812697"
},
"bid": "637b0fdd3d9a96eb913805d3",
"name": "sucess",
"slots": [
{
"date": "2022-11-28T00:00:00.000Z",
"slots": [
{
"uid": null,
"available": true,
"status": null,
"_id": {
"$oid": "638455dee12f0122c98126a0"
}
},
{
"uid": null,
"available": true,
"status": null,
"_id": {
"$oid": "638455dee12f0122c98126a1"
}
}
],
"_id": {
"$oid": "638455dee12f0122c9812698"
}
}
]}]}
im trying to update the slots for id 638455dee12f0122c98126a0 properties like
uid:'234235'
available:false
status:'booked'
can anyone help me to query this
i tried
const result = await Event.findOne({
'slots.slots': {
$elemMatch: {
_id: req.body.id
}
}
})
is there is any way to query this type of documents.
You can use $[] and $[<identifier>]:
db.collection.update({
"slots.slots": {
$elemMatch: {
_id: ObjectId("638455dee12f0122c98126a0")
}
}
},
{
$set: {
"slots.$[].slots.$[item].available": false,
"slots.$[].slots.$[item].uid": "234235",
"slots.$[].slots.$[item].status": "booked"
}
},
{
arrayFilters: [
{
"item._id": ObjectId("638455dee12f0122c98126a0")
}
]
})
See how it works on the playground example

How to perform two group bys in one aggregation query in mongoose?

I'm a beginner trying to create an API using express ang mongodb and this is my first time posting a question here, so forgive me if my question sounds stupid.
I have this data:
Restaurants
[
{
"_id": "630c0d5e82d52d0852d34b11",
"name": "Restaurant 1",
"type": "Fine Dining",
"cuisine": "Korean",
},
{
"_id": "630c0d5e82d52d0852d34b12",
"name": "Restaurant 2",
"type": "Fine Dining",
"cuisine": "International",
},
{
"_id": "630c0d5e82d52d0852d34b13",
"name": "Restaurant 3",
"type": "Casual Dining",
"cuisine": "Korean",
},
{
"_id": "630c0d5e82d52d0852d34b14",
"name": "Restaurant 4",
"type": "Casual Dining",
"cuisine": "International",
},
...
]
so what I want to achieve is when I create a GET request to this API endpoint http://localhost:8000/api/v1/restaurants/stats is this:
{
"status": "success",
"data": {
"cuisines": [
{
"_id": "Korean",
"restaurants": ["630c0d5e82d52d0852d34b11", "630c0d5e82d52d0852d34b13"],
"quantity": 2
},
{
"_id": "International",
"restaurants": ["630c0d5e82d52d0852d34b12", "630c0d5e82d52d0852d34b14"],
"quantity": 2
}
],
"type": [
{
"_id": "Casual Dining",
"restaurants": ["630c0d5e82d52d0852d34b14", "630c0d5e82d52d0852d34b13"],
"quantity": 2
},
{
"_id": "Fine Dining",
"restaurants": ["630c0d5e82d52d0852d34b11", "630c0d5e82d52d0852d34b12"],
"quantity": 2
}
]
}
}
This is what I have tried so far
restaurantController.js
...
exports.getRestaurantStats = async (req, res) => {
const cuisines = await Restaurant.aggregate([
{
$group: {
_id: '$cuisine',
restaurants: { $push: '$_id' },
quantity: { $sum: 1 },
}
},
]);
res.status(200).json({
status: 'success',
data: {
cuisines,
},
});
};
...
restaurantRoutes.js
...
router.route('/stats').get(restaurantController.getRestaurantStats);
...
The result for calling this endpoint http://localhost:8000/api/v1/restaurants/stats
{
"status": "success",
"data": {
"cuisines": [
{
"_id": "Korean",
"restaurants": ["630c0d5e82d52d0852d34b11", "630c0d5e82d52d0852d34b13"],
"quantity": 2
},
{
"_id": "International",
"restaurants": ["630c0d5e82d52d0852d34b12", "630c0d5e82d52d0852d34b14"],
"quantity": 2
}
]
}
But when I add this another group by
...
$group: {
_id: '$type',
restaurants: { $push: '$_id' },
quantity: { $sum: 1 },
}
...
to restaurantController.js
...
exports.getRestaurantStats = async (req, res) => {
const cuisines = await Restaurant.aggregate([
{
$group: {
_id: '$cuisine',
restaurants: { $push: '$_id' },
quantity: { $sum: 1 },
},
$group: {
_id: '$type',
restaurants: { $push: '$_id' },
quantity: { $sum: 1 },
}
},
]);
res.status(200).json({
status: 'success',
data: {
cuisines,
},
});
};
...
the result is different. Its seems like it overwrote the first group by in the pipeline.

MongoDb aggregation with arrays inside an array possible

I am struggling to find some examples of using the mongo aggregation framework to process documents which has an array of items where each item also has an array of other obejects (array containing an array)
In the example document below what I would really like is an example that sums the itemValue in the results array of all cases in the document and accross the collection where the result.decision was 'accepted'and group by the document locationCode
However, even an example that found all documents where the result.decision was 'accepted' to show or that summmed the itemValue for the same would help
Many thanks
{
"_id": "333212",
"data": {
"locationCode": "UK-555-5566",
"mode": "retail",
"caseHandler": "A N Other",
"cases": [{
"caseId": "CSE525666",
"items": [{
"id": "333212-CSE525666-1",
"type": "hardware",
"subType": "print cartridge",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "acme print cartridge",
"quantity": 2,
"weight": "1.5"
},
"result": {
"decision": "rejected",
"decisionDate": "2019-02-02"
},
"isPriority": true
},
{
"id": "333212-CSE525666-2",
"type": "Stationery",
"subType": "other",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "staples box",
"quantity": 3,
"weight": "1.66"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-03",
"itemValue": "23.01"
},
"isPriority": true
}
]
},
{
"caseId": "CSE885655",
"items": [{
"id": "333212-CSE885655-1",
"type": "marine goods",
"subType": "fish food",
"targetDate": "2020-06-04",
"itemDetail": {
"description": "fish bait",
"quantity": 5,
"weight": "0.65"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-02"
},
"isPriority": false
},
{
"id": "333212-CSE885655-4",
"type": "tobacco products",
"subType": "cigarettes",
"deadlineDate": "2020-06-15",
"itemDetail": {
"description": "rolling tobbaco",
"quantity": 42,
"weight": "2.25"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-02-02",
"itemValue": "48.15"
},
"isPriority": true
}
]
}
]
},
"state": "open"
}
You're probably looking for $unwind. It takes an array within a document and creates a separate document for each array member.
{ foos: [1, 2] } -> { foos: 1 }, { foos: 2}
With that you can create a flat document structure and match & group as normal.
db.collection.aggregate([
{
$unwind: "$data.cases"
},
{
$unwind: "$data.cases.items"
},
{
$match: {
"data.cases.items.result.decision": "accepted"
}
},
{
$group: {
_id: "$data.locationCode",
value: {
$sum: {
$toDecimal: "$data.cases.items.result.itemValue"
}
}
}
},
{
$project: {
_id: 0,
locationCode: "$_id",
value: "$value"
}
}
])
https://mongoplayground.net/p/Xr2WfFyPZS3
Alternative solution...
We group by data.locationCode and sum all items with this condition:
cases[*].items[*].result.decision" == "accepted"
db.collection.aggregate([
{
$group: {
_id: "$data.locationCode",
itemValue: {
$sum: {
$reduce: {
input: "$data.cases",
initialValue: 0,
in: {
$sum: {
$concatArrays: [
[ "$$value" ],
{
$map: {
input: {
$filter: {
input: "$$this.items",
as: "f",
cond: {
$eq: [ "$$f.result.decision", "accepted" ]
}
}
},
as: "item",
in: {
$toDouble: {
$ifNull: [ "$$item.result.itemValue", 0 ]
}
}
}
}
]
}
}
}
}
}
}
}
])
MongoPlayground

mongo query: remove object when objects has matching values (email)

I'm trying to find all the objects with type==1 (works already) and, then check duplicates from email addresses. (not working)
Output would be something like this:
[ { _id:
{ id: 59d0e3f728150e39b008013f,
firstName: 'pera',
lastName: 'pekko',
phone: '04040404040',
massageType: 'jipii massage',
time: 45,
price: 30,
startsAt: '2017-10-02T11:00:00+03:00',
endsAt: '2017-10-02T11:45:00+03:00' },
email: [ 'gg#gmail.com' ],
{ _id:
{ id: 59ce25975f4d1a096cdd2733,
firstName: 'sdad',
lastName: 'autismus',
phone: '04040404040',
massageType: 'jipii massage',
time: 60,
price: 33,
startsAt: '2017-10-02T12:45:00+03:00',
endsAt: '2017-10-02T13:45:00+03:00' },
email: [ 'YES#hotmail.com' ]
}]
So basically it would check duplicate "email"-values. If there is a duplicate value --> remove WHOLE object from result.
My code so far (result prints all type==1 objects WITH duplicates):
db3.massagerTimes.aggregate(
{$match: {_id: mongo.helper.toObjectID(data.timesData)}},
{$unwind: "$times"},
{$match: {"times.type": 1}},
{
$group: {
_id: {
id: "$times._id",
firstName: "$times.firstName",
lastName: "$times.lastName",
phone: "$times.phone",
massageType: "$times.massageType",
time: "$times.time",
price: "$times.price",
startsAt: "$times.startsAt",
endsAt: "$times.endsAt"
},
email: {$addToSet: "$times.email"},
count: {"$sum": 1}
}
},
{
$match: {
count: {"$lte": 1}
}
},
{$sort: {"times._id": -1}},
function (err, result) {
if (err)
throw err;
if (result) {
console.log(result);
res.json(result);
}
});
}
Collection:
{
"_id": {
"$oid": "59ca4eb610b3be1f8499f6d5"
},
"times": [
{
"_id": {
"$oid": "59ce274f5f4d1a096cdd2745"
},
"type": 0,
"startsAt": "2017-10-02 13:30 +03:00",
"endsAt": "2017-10-02 13:45 +03:00"
},
{
"_id": {
"$oid": "59ce274f5f4d1a096cdd2744"
},
"type": 0,
"startsAt": "2017-10-02 13:15 +03:00",
"endsAt": "2017-10-02 13:30 +03:00"
},
{
"_id": {
"$oid": "59ce28415f4d1a096cdd275b"
},
"title": "sdad autismus",
"firstName": "sdad",
"lastName": "autismus",
"type": 1,
"email": "YES#hotmail.com",
"phone": "04040404040",
"massageType": "jipii massage",
"time": 45,
"price": 30,
"additionalInfo": null,
"startsAt": "2017-10-01T08:30:00+03:00",
"endsAt": "2017-10-01T09:15:00+03:00"
},
{
"_id": {
"$oid": "59ce28665f4d1a096cdd275e"
},
"title": "sdad autismus",
"firstName": "sdad",
"lastName": "autismus",
"type": 1,
"email": "YES#hotmail.com",
"phone": "04040404040",
"massageType": "jipii massage",
"time": 45,
"price": 30,
"additionalInfo": null,
"startsAt": "2017-10-01T09:30:00+03:00",
"endsAt": "2017-10-01T10:15:00+03:00"
},
{
"_id": {
"$oid": "59d0e3f728150e39b008013f"
},
"title": "pera pekko",
"firstName": "pera",
"lastName": "pekko",
"type": 1,
"email": "gg#gmail.com",
"phone": "04040404040",
"massageType": "some massage",
"time": 45,
"price": 30,
"additionalInfo": null,
"startsAt": "2017-10-02T11:00:00+03:00",
"endsAt": "2017-10-02T11:45:00+03:00"
}
]
}
Question: How I check duplicates from value and then drop it from results?
(in this case YES#hotmail.com <-- 2x, remove duplicate object, not just the duplicate value)
The core of what you need would be something like that:
db3.massagerTimes.aggregate({
$unwind: "$times"
}, {
$group: {
_id: "$times.email", // group all items with the same email together
doc: {$first: "$$ROOT"} // keep only the first document for each email address
}
}, {
$replaceRoot: {
newRoot: "$doc" // move the element we remembered during the grouping stage to the top of our document
}
});