Mongo Aggregation Get Difference between values of array objects - arrays

Good day, I am having difficulty using aggregation in mongodb to get the date difference between two values in an array, my document structure looks as follows:
{
"_id" : ObjectId("5c591420a890362dec70fcf4"),
"tracetag" : "T00005",
"amounts" : [
{
"_id" : ObjectId("5c5915bea890362dec71015a"),
"startdate" : ISODate("2019-02-15T04:49:02.000+0000"),
"enddate" : ISODate("2019-02-15T04:50:05.000+0000"),
"amount" : 18.975946800811833,
},
{
"_id" : ObjectId("5c5915bea890362dec71015b"),
"startdate" : ISODate("2019-02-16T04:49:02.000+0000"),
"enddate" : ISODate("2019-02-16T04:51:52.000+0000"),
"amount" : 3.5755730555836203,
},
{
"_id" : ObjectId("5c5915bea890362dec71015c"),
"startdate" : ISODate("2019-02-17T04:49:02.000+0000"),
"enddate" : ISODate("2019-02-17T04:50:04.000+0000"),
"amount" : 2.3937573313380383,
}
],
}
the number of "amounts" entries are not fixed, so it could be 1 or 50 etc.
for each document, I would like to get the difference between the 'enddate' and 'startdate' (aka the duration) and I would like to sum the duration (lets call this totalduration). I can do the total 'amount' without issues, but getting the duration gives me error ""cant $subtract aarray from a array", my aggregation looks like this:
{
"$project" : {
"tracetag" : 1.0,
"totalamount" : {
"$sum" : "$amounts.amount"
},
"totalduration" : {
"$sum" : {
"$subtract" : [
"$amounts.enddate",
"$amounts.startdate"
]
}
}
}
},

OK so I managed to answer my own question: the 'amounts' need to be split using the $unwind operator, then a field can be added to each record (duration), in my case I wanted the duration in minutes. Afterwards I grouped everything again
{
"$unwind" : { "path" : "$amounts" }
},
{
"$addFields" : {
"duration" : { "$divide" : [ { "$subtract" : [
"$enddate", "$startdate" ]}, 60000.0 ] }
}
},

Related

How update my objects in a document's array in mongoDB

i have the following collection, I have one question about:
{
"_id" : ObjectId("123456789"),
"user_id" : 123456,
"total" : 100,
"items" : [
{
"item_name" : "my_item_one",
"price" : 30
},
{
"item_name" : "my_item_two",
"price" : 30
},
{
"item_name" : "my_item_three",
"price" : 30
}
]
}
how i can update all price concurrently, (price=50 in all objects)
thanks for help
You can use positional all operator to update all elements in the array
db.collectionName.update(
{ },
{ $set: { "items.$[].price" : 50 } },
{ multi: true }
)

Problems with JSON

I'm trying to Order in ascending order the requirements that are not between 25,000 and 30,000. The requirements are information of an array which is inside of a document and the condition is that they can't be in that determined range. I'm trying to do it properly but i can't find any information about this.
Document structure
{
"_id" : ObjectId("5ec73abebd7e4d618a05734e"),
"code" : "A47",
"title" : "Software engineer",
"description" : "Analyze, design, create, test computer and software systems.",
"city" : "Madrid",
"date" : ISODate("2020-05-22T02:36:46.271Z"),
"salary" : 30000.0,
"active" : true,
"requirements" : [
"python",
"java",
"html5",
"C++",
"C#"
],
"info_company" : {
"cif" : "A00000000",
"name" : "FUTURE S.A",
"location" : "Madrid",
"web" : "www.future.es",
"about" : "We are a leading company in new technologies."
},
"pyme" : true
}
db.offers.update(
{ $ne : [ salary: {
$gte : 25000,
$lte : 35000 ]
} },
{
$push : {
requirements: {
$each : [] ,
$sort : 1
}
}
}
,
{
multi : true
} )

Getting Specific Array Element - Meteor Mongodb

I have this structure
{
"_id" : "EbtLm2Nmb79WWryEr",
"notificationByUsers" : {
"all" : [
{
"account_id" : "X5PjY66JAwgoxDb4L",
"date" : ISODate("2016-07-27T13:48:17.154Z"),
"value" : null
},
{
"account_id" : "2C2FKXaKtmeRMNT3E",
"date" : ISODate("2016-07-27T13:53:10.296Z"),
"value" : "Instant"
},
{
"account_id" : "6Np35oj63cavF4RHs",
"date" : ISODate("2016-07-28T07:18:22.696Z"),
"value" : "Instant"
}
]
}
}
and i am querying
db.Collection.findOne({_id: EbtLm2Nmb79WWryEr, 'notificationByUsers.all':{$elemMatch:{account_id: "2C2FKXaKtmeRMNT3E"}}}, {_id:0, 'notificationByUsers.all.$': 1})
it returns in roboMongo
{
"notificationByUsers" : {
"all" : [
{
"account_id" : "2C2FKXaKtmeRMNT3E",
"date" : ISODate("2016-07-27T13:53:10.296Z"),
"value" : "Instant"
}
]
}
But in Meteor it returns all array elements with this query. I want the result with specific array element as working in robomongo.
You can do this using fields projection, like:
MyCollection.findOne({},{ fields : {_id:0, 'notificationByUsers.all':1}});
This will return object like :
{
"notificationByUsers" : {
"all" : [
{
"account_id" : "2C2FKXaKtmeRMNT3E",
"date" : ISODate("2016-07-27T13:53:10.296Z"),
"value" : "Instant"
}
]
}

Insert value into array element in an array element and return array length

I would like to add a value to an a field which lies within an array element, I would also like the amount of elements in the array to be returned and also that only one value is allowed to be input. below is the data:
{
"_id" : ObjectId("56bb59beb32fa53064f51e3f"),
"title" : "okok",
"views" : 1,
"messages" : [
{
"authorId" : ObjectId("56bb599e8f308f1664c93011"),
"upvotes" : [], // <--- want to push a value here
"created" : ISODate("2016-02-10T15:39:42.006Z"),
"updated" : ISODate("2016-02-10T15:39:42.006Z"),
"message" : "okok"
},
{
"authorId" : ObjectId("56bb599e8f308f1664c93010"),
"upvotes" : [], // <--- insert into here plz
"created" : ISODate("2016-02-10T15:39:47.170Z"),
"updated" : ISODate("2016-02-10T15:39:47.170Z"),
"message" : "uhuhuh"
},
{
"authorId" : ObjectId("56bb599e8f308f1664c93011"),
"upvotes" : [],
"created" : ISODate("2016-02-10T15:40:01.772Z"),
"updated" : ISODate("2016-02-10T15:40:01.772Z"),
"message" : "åpåpå"
},
{
"authorId" : ObjectId("56bb599e8f308f1664c93010"),
"upvotes" : [],
"created" : ISODate("2016-02-10T15:40:04.889Z"),
"updated" : ISODate("2016-02-10T15:40:04.889Z"),
"message" : "påpåpå<br /><br />påå"
},
{
"authorId" : ObjectId("56bb599e8f308f1664c93010"),
"upvotes" : [],
"created" : ISODate("2016-02-11T12:36:26.006Z"),
"updated" : ISODate("2016-02-11T12:36:26.006Z"),
"message" : "testt"
},
{
"authorId" : ObjectId("56bb599e8f308f1664c93013"),
"upvotes" : [],
"created" : ISODate("2016-02-11T12:36:31.514Z"),
"updated" : ISODate("2016-02-11T12:36:31.514Z"),
"message" : "tetetet"
}
]
}
I have the document _id and the array element created field to locate the element in the array.
I tried the following:
}
$match: {
_id: retard.ObjectId(data.id),
'messages.created': data.created
}
}, {
$group: {
'messages.$.upvotes': {
$addToSet: 'myTestValueToInsert'
}
}
}, {
$project: {
numUpvotes: {
$size: '$upvotes'
}
}
}, {
multi: false
}
However getting an error. I've been reading the aggregation documentation but the examples are slightly lacking!
The aggregation framework is not intended to and cannot be used to insert or update documents(this is an extended capability and not an exception), rather, it can process data records and return computed results.
You could use the update query to update matching sub document:
var myTestValueToInsert = 1;
db.t.update({
"_id":retard.ObjectId(data.id),
"messages.created":data.created
},
{
"$addToSet":{"messages.$.upvotes":myTestValueToInsert}
})
Then, to retrieve the size of the modified array, you could aggregate it as below:
db.t.aggregate([
{$match:{"_id":retard.ObjectId(data.id)}},
{$unwind:"$messages"},
{$match:{"messages.created":data.created}},
{$project:{"size":{$size:"$messages.upvotes"},"_id":0}}
])
If you are using the shell, there is a nice wrapper to update and get the updated document, but you would need to get the size of the array inside the document in the client side:
db.t.findAndModify({
"query":{"_id":retard.ObjectId(data.id),"messages.created":data.created},
"update":{$addToSet:{"messages.$.upvotes":myTestValueToInsert}},
"new":true
})

Mongoose Query: Find an element inside an array

Mongoose/Mongo noob here:
My Data
Here is my simplified data, each user has his own document
{ "__v" : 1,
"_id" : ObjectId( "53440e94c02b3cae81eb0065" ),
"email" : "test#test.com",
"firstName" : "testFirstName",
"inventories" : [
{ "_id" : "active",
"tags" : [
"inventory",
"active",
"vehicles" ],
"title" : "activeInventory",
"vehicles" : [
{ "_id" : ObjectId( "53440e94c02b3cae81eb0069" ),
"tags" : [
"vehicle" ],
"details" : [
{ "_id" : ObjectId( "53440e94c02b3cae81eb0066" ),
"year" : 2007,
"transmission" : "Manual",
"price" : 1000,
"model" : "Firecar",
"mileageReading" : 50000,
"make" : "Bentley",
"interiorColor" : "blue",
"history" : "CarProof",
"exteriorColor" : "blue",
"driveTrain" : "SWD",
"description" : "test vehicle",
"cylinders" : 4,
"mileageType" : "kms" } ] } ] },
{ "title" : "soldInventory",
"_id" : "sold",
"vehicles" : [],
"tags" : [
"inventory",
"sold",
"vehicles" ] },
{ "title" : "deletedInventory",
"_id" : "deleted",
"vehicles" : [],
"tags" : [
"inventory",
"sold",
"vehicles" ] } ] }
As you can see, each user has an inventories property that is an array that contains 3 inventories (activeInventory, soldInventory and deletedInventory)
My Query
Given an user's email a a vehicle ID, i would like my query to go through find the user's activeInventory and return just the vehicle that matches the ID. Here is what I have so far:
user = api.mongodb.userModel;
ObjectId = require('mongoose').Types.ObjectId;
return user
.findOne({email : params.username})
.select('inventories')
.find({'title': 'activeInventory'})
//also tried
//.where('title')
//.equals('activeInventory')
.exec(function(err, result){
console.log(err);
console.log(result);
});
With this, result comes out as an empty array. I've also tried .find('inventories.title': 'activeInventory') which strangely returns the entire inventories array. If possible, I'd like to keep the chaining query format as I find it much more readable.
My Ideal Query
return user
.findOne({email : params.username})
.select('inventories')
.where('title')
.equals('activeInventory')
.select('vehicles')
.id(vehicleID)
.exec(cb)
Obviously it does not work but it can give you an idea what I'm trying to do.
Using the $ positional operator, you can get the results. However, if you have multiple elements in the vehicles array all of them will be returned in the result, as you can only use one positional operator in the projection and you are working with 2 arrays (one inside another).
I would suggest you take a look at the aggregation framework, as you'll get a lot more flexibility. Here's an example query for your question that runs in the shell. I'm not familiar with mongoose, but I guess this will still help you and you'd be able to translate it:
db.collection.aggregate([
// Get only the documents where "email" equals "test#test.com" -- REPLACE with params.username
{"$match" : {email : "test#test.com"}},
// Unwind the "inventories" array
{"$unwind" : "$inventories"},
// Get only elements where "inventories.title" equals "activeInventory"
{"$match" : {"inventories.title":"activeInventory"}},
// Unwind the "vehicles" array
{"$unwind" : "$inventories.vehicles"},
// Filter by vehicle ID -- REPLACE with vehicleID
{"$match" : {"inventories.vehicles._id":ObjectId("53440e94c02b3cae81eb0069")}},
// Tidy up the output
{"$project" : {_id:0, vehicle:"$inventories.vehicles"}}
])
This is the output you'll get:
{
"result" : [
{
"vehicle" : {
"_id" : ObjectId("53440e94c02b3cae81eb0069"),
"tags" : [
"vehicle"
],
"details" : [
{
"_id" : ObjectId("53440e94c02b3cae81eb0066"),
"year" : 2007,
"transmission" : "Manual",
"price" : 1000,
"model" : "Firecar",
"mileageReading" : 50000,
"make" : "Bentley",
"interiorColor" : "blue",
"history" : "CarProof",
"exteriorColor" : "blue",
"driveTrain" : "SWD",
"description" : "test vehicle",
"cylinders" : 4,
"mileageType" : "kms"
}
]
}
}
],
"ok" : 1
}
getting the chaining query format ... i dont know how to parse it but, what you are searching for is projection, you should take a look to http://docs.mongodb.org/manual/reference/operator/projection/
it would probably look like this :
user.findOne({email: params.username}, {'inventories.title': {$elemMatch: "activeInventory", 'invertories.vehicle.id': $elemMatch: params.vehicleId}, function(err, result) {
console.log(err);
console.log(result);
})

Resources