I have this data structure in my MongoDB database:
"menu": [
{
"dishCategory":"61e6089f209b802518e2b4a4",
"dishMeals": [
{
"dishMealName": "Burger King",
"dishMealIngredients": "Burger lepinja, bbq sos, berlin sos, zelena"
"dishMealPrice": 5
}
]
}
]
How do I push a new object inside dishMeals in exact dishCategory ( I am providing dishCategoryId, newDish object and _id of restaurant through req.body) I've tried this but nothing is changing:
await Restaurants.updateOne(
{
'_id' : _id,
'menu.dishCategory' : dishCategoryId
},
{
$push : {
'menu.$.dishMeals' : newDish
}
}
);
Use arrayFilters to filter the nested document(s) in the array field, then $push if the filter criteria in the arrayFilters met.
db.collection.update({
"_id": _id,
"menu.dishCategory": dishCategoryId
},
{
$push: {
"menu.$[menu].dishMeals": newDish
}
},
{
arrayFilters: [
{
"menu.dishCategory": dishCategoryId
}
]
})
Sample Mongo Playground
You can do it with arrayFilters config in update query:
db.collection.update({
"restaurant_id": 1
},
{
"$push": {
"menu.$[element].dishMeals": {
"dishMealName": "Best Burger",
"dishMealIngredients": "Best burger in town",
"dishMealPrice": 10
}
}
},
{
"arrayFilters": [
{
"element.dishCategory._id": "61e6089f209b802518e2b4a4"
}
]
})
Working example
You may read the question and the solution they provided here, Hope this one will be helpful to you.
db.collection.update({
"_id": 1,
"menu.dishCategory": "61e6089f209b802518e2b4a4"
},
{
$push: {
"menu.$.dishMeals": newMeal
}
})
Sample Example
Related
My data looks like this
{
"_id": "62f77d806f24c09f0acae163",
"name": "Test product",
"attributes": [
{
"attribute_name": "Shape",
"attribute_value": "Square"
},
{
"attribute_name": "Color",
"attribute_value": "Red"
}
]
}
I am using the aggregate method to filter results where I want to find products where "attribute_name" is "shape" and the "attribute_value" is "Square" AND "attribute_name" is "Color" and the "attribute_value" is "Red"
Basically I am building a filter feature in my application and basis the data passed to the API I want to get the products.
I have tried this:
let lookup = {
$match: {
$and: [
{
'attributes.attribute_label': 'Shape',
'attributes.attribute_value': {
$in: ['Square']
},
},
{
'attributes.attribute_label': 'Color',
'attributes.attribute_value': {
$in: ['Red']
},
}
],
}
};
let products = await productsModel.aggregate(lookup);
At first it seemed like it worked, but then I noticed it doesn't work properly, it matches
'attributes.attribute_value': {
$in: ['Red']
},
so if it finds "Red" in "attribute_label" which can be anything other than "Color" it will still return the results.
Any help is appreciated
I want to be able to get results based on the values for each attribute name
For e.g data passed might be this
Shape=Square,Color=Red,Green
I want to get the products which matches this, where the object with attribute_label of Color contains the attribute_value of Red or Green.
Is it a typo? Once you are using "attributes.attribute_label" and once "attributes.attribute_name".
This should work with attributes.attrubite_name (not label!)
[
{
'$match': {
'$and': [
{
'attributes.attribute_name': 'Shape'
}, {
'attributes.attribute_value': {
'$in': [
'Square'
]
}
}, {
'attributes.attribute_name': 'Color'
}, {
'attributes.attribute_value': {
'$in': [
'Red'
]
}
}
]
}
}
]
I am working on an express js application where I need to update a nested array.
1) Schema :
//Creating a mongoose schema
var userSchema = mongoose.Schema({
_id: {type: String, required:true},
name: String,
sensors: [{
sensor_name: {type: String, required:true},
measurements: [{time: String}]
}] });
2)
Here is the code snippet and explanation is below:
router.route('/sensors_update/:_id/:sensor_name/')
.post(function (req, res) {
User.findOneAndUpdate({_id:req.body._id}, {$push: {"sensors" :
{"sensor_name" : req.body.sensor_name , "measurements.0.time": req.body.time } } },
{new:true},function(err, newSensor) {
if (err)
res.send(err);
res.send(newSensor)
}); });
I am able to successfully update a value to the measurements array using the findOneAndUpdate with push technique but I'm failing when I try to add multiple measurements to the sensors array.
Here is current json I get if I get when I post a second measurement to the sensors array :
{
"_id": "Manasa",
"name": "Manasa Sub",
"__v": 0,
"sensors": [
{
"sensor_name": "ras",
"_id": "57da0a4bf3884d1fb2234c74",
"measurements": [
{
"time": "8:00"
}
]
},
{
"sensor_name": "ras",
"_id": "57da0a68f3884d1fb2234c75",
"measurements": [
{
"time": "9:00"
}
]
}]}
But the right format I want is posting multiple measurements with the sensors array like this :
Right JSON format would be :
{
"_id" : "Manasa",
"name" : "Manasa Sub",
"sensors" : [
{
"sensor_name" : "ras",
"_id" : ObjectId("57da0a4bf3884d1fb2234c74"),
"measurements" : [
{
"time" : "8:00"
}
],
"measurements" : [
{
"time" : "9:00"
}
]
}],
"__v" : 0 }
Please suggest some ideas regarding this. Thanks in advance.
You might want to rethink your data model. As it is currently, you cannot accomplish what you want. The sensors field refers to an array. In the ideal document format that you have provided, you have a single object inside that array. Then inside that object, you have two fields with the exact same key. In a JSON object, or mongo document in this context, you can't have duplicate keys within the same object.
It's not clear exactly what you're looking for here, but perhaps it would be best to go for something like this:
{
"_id" : "Manasa",
"name" : "Manasa Sub",
"sensors" : [
{
"sensor_name" : "ras",
"_id" : ObjectId("57da0a4bf3884d1fb2234c74"),
"measurements" : [
{
"time" : "8:00"
},
{
"time" : "9:00"
}
]
},
{
// next sensor in the sensors array with similar format
"_id": "",
"name": "",
"measurements": []
}],
}
If this is what you want, then you can try this:
User.findOneAndUpdate(
{ _id:req.body._id "sensors.sensor_name": req.body.sensor_name },
{ $push: { "sensors.0.measurements": { "time": req.body.time } } }
);
And as a side note, if you're only ever going to store a single string in each object in the measurements array, you might want to just store the actual values instead of the whole object { time: "value" }. You might find the data easier to handle this way.
Instead of hardcoding the index of the array it is possible to use identifier and positional operator $.
Example:
User.findOneAndUpdate(
{ _id: "Manasa" },
{ $push: { "sensors.$[outer].measurements": { "time": req.body.time } } }
{ "arrayFilters:" [{"outer._id": ObjectId("57da0a4bf3884d1fb2234c74")}]
);
You may notice than instead of getting a first element of the array I specified which element of the sensors array I would like to update by providing its ObjectId.
Note that arrayFilters are passed as the third argument to the update query as an option.
You could now make "outer._id" dynamic by passing the ObjectId of the sensor like so: {"outer._id": req.body.sensorId}
In general, with the use of identifier, you can get to even deeper nested array elements by following the same procedure and adding more filters.
If there was a third level nesting you could then do something like:
User.findOneAndUpdate(
{ _id: "Manasa" },
{ $push: { "sensors.$[outer].measurements.$[inner].example": { "time": req.body.time } } }
{ "arrayFilters:" [{"outer._id": ObjectId("57da0a4bf3884d1fb2234c74"), {"inner._id": ObjectId("57da0a4bf3884d1fb2234c74"}}]
);
You can find more details here in the answer written by Neil Lunn.
refer ::: positional-all
--- conditions :: { other_conditions, 'array1.array2.field_to_be_checked': 'value' }
--- updateData ::: { $push : { 'array1.$[].array2.$[].array3' : 'value_to_be_pushed' } }
I am trying to implement a function that collects unread messages from an articles collection. Each article in the collection has a "discussions" entry with discussion comment subdocuments. An example of such a subdocument is:
{
"id": NumberLong(7534),
"user": DBRef("users", ObjectId("...")),
"dt_create": ISODate("2015-01-26T00:10:44Z"),
"content": "The discussion comment content"
}
The parent document has the following (partial) structure:
{
model: {
id: 17676,
title: "Article title",
author: DBRef("users", ObjectId(...)),
// a bunch of other fields here
},
statistics: {
// Statistics will be stored here (pageviews, etc)
},
discussions: [
// Array of discussion subdocuments, like the one above
]
}
Each user also has a last_viewed entry which is a document, an example is as follows:
{
"17676" : "2015-01-10T00:00:00.000Z",
"18038" : "2015-01-10T00:00:00.000Z",
"18242" : "2015-01-20T00:00:00.000Z",
"18325" : "2015-01-20T00:00:00.000Z"
}
This means that the user has looked at discussion comments for the last time on January 10th 2015 for articles with IDs 17676 and 18038, and on January 20th 2015 for articles with IDs 18242 and 18325.
So I want to collect discussion entries from the article documents, and for article with ID 17676, I want to collect the discussion entries that were created after 2015-01-10, and for article with ID 18242, I want to show the discussion entries created after 2015-01-20.
UPDATED
Based on Neil Lunn's reply, the function I have created so far is:
function getUnreadDiscussions(userid) {
user = db.users.findOne({ 'model.id': userid });
last_viewed = [];
for(var i in user.last_viewed) {
last_viewed.push({
'id': parseInt(i),
'dt': user.last_viewed[i]
});
}
result = db.articles.aggregate([
// For now, collect just articles the user has written
{ $match: { 'model.author': DBRef('users', user._id) } },
{ $unwind: '$discussions' },
{ $project: {
'model': '$model',
'discussions': '$discussions',
'last_viewed': {
'$let': {
'vars': { 'last_viewed': last_viewed },
'in': {
'$setDifference': [
{ '$map': {
'input': '$$last_viewed',
'as': 'last_viewed',
'in': {
'$cond': [
{ '$eq': [ '$$last_viewed.id', '$model.id' ] },
'$$last_viewed.dt',
false
]
}
} },
[ false ]
]
}
}
}
}
},
// To get a scalar instead of a 1-element array:
{ $unwind: '$last_viewed' },
// Match only those that were created after last_viewed
{ $match: { 'discussions.dt_create': { $gt: '$last_viewed' } } },
{ $project: {
'model.id': 1,
'model.title': 1,
'discussions': 1,
'last_viewed': 1
} }
]);
return result.toArray();
}
The whole $let thing, and the $unwind after that, transforms the data into the following partial projection (with the last $match commented out):
{
"_id" : ObjectId("54d9af1dca71d8054c8d0ee3"),
"model" : {
"id" : NumberLong(18325),
"title" : "Article title"
},
"discussions" : {
"id" : NumberLong(7543),
"user" : DBRef("users", ObjectId("54d9ae24ca71d8054c8b4567")),
"dt_create" : ISODate("2015-01-26T00:10:44Z"),
"content" : "Some comment here"
},
"last_viewed" : ISODate("2015-01-20T00:00:00Z")
},
{
"_id" : ObjectId("54d9af1dca71d8054c8d0ee3"),
"model" : {
"id" : NumberLong(18325),
"title" : "Article title"
},
"discussions" : {
"id" : NumberLong(7554),
"user" : DBRef("users", ObjectId("54d9ae24ca71d8054c8b4567")),
"dt_create" : ISODate("2015-01-26T02:03:22Z"),
"content" : "Another comment here"
},
"last_viewed" : ISODate("2015-01-20T00:00:00Z")
}
So far so good here. But the problem now is that the $match to select only the discussions created after the last_viewed date is not working. I am getting an empty array response. However, if I hard-code the date and put in $match: { 'discussions.dt_create': { $gt: ISODate("2015-01-20 00:00:00") } }, it works. But I want it to take it from last_viewed.
I found another SO thread where this issue has been resolved by using the $cmp operator.
The final part of the aggregation would be:
[
{ /* $match, $unwind, $project, $unwind as before */ },
{ $project: {
'model': 1,
'discussions': 1,
'last_viewed': 1,
'compare': {
$cmp: [ '$discussions.dt_create', '$last_viewed' ]
}
} },
{ $match: { 'compare': { $gt: 0 } } }
]
The aggregation framework is great, but it takes quite a different approach in problem-solving. Hope this helps anyone!
I'll keep the question unanswered in case anyone else has a better answer/method. If this answer has been upvoted enough times, I'll accept this one.
I am using the method from this question How to filter array in subdocument with MongoDB
It works as expected except when none of the elements in the array match the test. In that case, I just get an empty array with no parent data.
SAMPLE DATA
{
"_id": "53712c7238b8d900008ef71c",
"dealerName": "TestDealer",
"email": "test#test.com",
"address": {..},
"inventories": [
{
"title": "active",
"vehicles": [
{
"_id": "53712fa138b8d900008ef720",
"createdAt": "2014-05-12T20:08:00.000Z",
"tags": [
"vehicle"
],
"opts": {...},
"listed": false,
"disclosures": {...},
"details": {...}
},
{
"_id": "53712fa138b8d900008ef720",
"createdAt": "2014-05-12T20:08:00.000Z",
"tags": [...],
"opts": {...},
"listed": true,
"disclosures": {...},
"details": {...}
}
]
},
{
"title": "sold",
"vehicles": []
}
]
}
TRYING TO DO
In my query I would like to return the user (document) top-level info (dealerName, email) and a property called vehicles containing all the vehicles in the "active" inventory that have the property listed set to true.
HOW FAR I GOT
This is my query. (I use Mongoose but use mostly native Mongo features)
{
$match:
email: params.username
}
{
$unwind: '$inventories'
}
{
$match:
'inventories.title': 'active'
}
{
$unwind:
'$inventories.vehicles'
}
{
$match:
'inventories.vehicles.listed':
$eq: true
}
{
$group:
_id: '$_id'
dealerName:
$first: '$dealerName'
email:
$first: '$email'
address:
$first: '$address'
vehicles:
$push: '$inventories.vehicles'
}
THE PROBLEM
At first, I thought my query was fine, however, if none of the vehicles are marked as listed, the query just returns an empty array. This makes sense since
{
$match:
'inventories.vehicles.listed':
$eq: true
}
Doesn't match anything but I would still like to get the dealerName as well as his email
DESIRED OUTPUT IF NO VEHICLES MATCH
[{"dealerName": "TestDealer", "email": "test#test.com", vehicles : []}]
ACTUAL OUTPUT
[]
You could use $redact instead of $match in this case, like this
db.collectionName.aggregate({
$redact:{
$cond:{
if:{$and:[{$not:"$dealerName"},{$not:"$title"},{$eq:["$listed",false]},
then: "$$PRUNE",
else: "$$DESCEND"
}
}
})
We need first condition to skip top level documents, second condition to skip second level and third one to prune vehicles. No $unwind needed in this case!
One more thing: $redact available only in 2.6
I have some data that looks like this (not real data):
{
_id:'cust04',
name:'Diarmuid Rellis',
address:'Elysium, Passage East',
county:'Waterford',
phone:'051-345786',
email:'dreil#drarch.com',
quotations:[
{
_id:'quot03',
supplier_ref:'A2006',
date_received: new Date('2013-05-12T00:00:00'),
date_returned: new Date('2013-05-15T00:00:00'),
supplier_price:35000.00,
customer_price:35000.00,
orders:[
{
_id:'ord03',
order_date: new Date('2013-05-20T00:00:00'),
del_date: new Date('2013-08-12T00:00:00'),
del_address:'Elysium, Passage East, Co. Waterford',
status:'BALPAID'
}
]
},
{
_id:'quot04',
supplier_ref:'A2007',
date_received: new Date('2013-08-10T00:00:00'),
date_returned: new Date('2013-08-12T00:00:00'),
supplier_price:29600.00,
customer_price:29600.00,
orders:[
{
_id:'ord04',
order_date: new Date('2014-03-20T00:00:00'),
del_date: new Date('2014-05-12T00:00:00'),
del_address:'Elysium, Passage East, Co. Waterford',
status:'INPROD'
}
]
}
]
}
I am trying to unwind the quotations and orders arrays, and get a projection of all orders in production which include the customer name, supplier_ref and order date for each.
Here is my query:
db.customers.aggregate([
{ $unwind: "$quotations" },
{ $unwind: "$quotations.orders" },
{ $match: { 'quotations.orders.status': 'INPROD' } },
{
$project: {
name: 1,
supplier_ref: "$quotations.supplier_ref",
order_id: "$quotations.orders._id",
order_date: "$quotations.orders.order_date"
}
},
{
$group: {
_id: "$order_id"
}
}
], function (err, results) {
console.log(results);
})
The query runs successfully, but just gives the order ids, not any of the other fields required. What am I missing ?
EDIT
I am hoping for a result like:
"result": [
{
"_id" : "orderid01",
"name" : "Joe Bloggs",
"supplier_ref" : "A1234",
"date_ordered" : "2012-04-14"
},
{
"_id" : "orderid02",
"name" : "Joe Bloggs",
"supplier_ref" : "A1235",
"date_ordered" : "2012-04-16"
}
]
When I add an extra field to my 'group' function, like so:
$group: {
_id: "$order_id",
supplier_ref: "$supplier_ref"
}
I get the error: "the group aggregate field 'supplier_ref' must be defined as an expression inside an object". Do I have to associate it with the result object in some way ?
Removing the group function altogether produced the results I wanted.