Dynamic keys from values in MongoDB - mongodb

Say I have this:
{
"_id" : "ENVD",
"years" : [
{
"year" : "2013",
"avgInstructor" : 5.144999999999998
},
{
"year" : "2012",
"avgInstructor" : 5.194436090225564
}
]
}
I need to be able to find the difference in the avgInstructor field from 2012-13. I was thinking I could transform the keys somehow using a $project which would make the year be the key, and the avgInstructor rating be the value. So it would look like this:
{
"_id" : "ENVD",
"years" : {
"2013" : 5.144999999999998,
"2012" : 5.194436090225564
}
}
Is this possible? Keep in mind my main goal is to be able to run a subtraction like this pseudocode : years['2013'].avgInstructor - years['2013'].avgInstructor. So if you see an easier way, that would be great as well. I am not sure of the best way to go about this in the context of an aggregation pipeline. Can someone help?

For people that ends up here looking for a solution to transform an array to an object using aggregation in a more recent version of MongoDB:
MongoDB 3.4.4 introduced $arrayToObject
You have to $map years
{
$set: {
years: {
$map: {
input: "$years",
as: "year",
in: [
"$$year.year",
"$$year.avgInstructor"
]
}
}
}
}
to be like this
{
"_id" : "ENVD",
"years": [
["2013", 5.144999999999998],
["2012", 5.194436090225564]
]
}
then use $arrayToObject
{
$set: {
years: {
$arrayToObject: "$years"
}
}
}
Or combine the two steps together
{
$set: {
years: {
$arrayToObject: {
$map: {
input: "$years",
as: "year",
in: [
"$$year.year",
"$$year.avgInstructor"
]
}
}
}
}
}

A Possible answer ...
Unwind first
project to make it a little easier to handle
Sort on _id and then on year
group by _id to pickup the first and the last value
final projection to get subtracted value
db.coll.aggregate ( [
{ "$unwind" : "$years" } ,
{ $project : { "year" : "$years.year", "avgInstructor" : "$years.avgInstructor" } },
{ $sort : { "_id" : 1, "year" : 1 } },
{ $group : { "_id" : "$_id", "val_min" : { $first : "$avgInstructor" }, "val_max" : { $last : "$avgInstructor" } } },
{ $project : { "diff" : { $subtract : [ "$val_min", "$val_max" ] } } }
] )

Related

How to convert multiple documents from a single collection to a single document containing one array

I have an aggregation pipeline that nearly does what I want. I've used match / unwind / project / sort to get 99% of the way. It is returning multiple documents:
[
{
"_id" : 254.8
},
{
"_id" : 93.7
},
{
"_id" : 89.9
},
{
"_id" : 94.15
},
{
"_id" : 102.1
},
{
"_id" : 93.9
},
{
"_id" : 102.7
}
]
Note: I've added the array brackets and commas to make it more readable, but you can also read it as:
{
"_id" : 254.8
}
{
"_id" : 93.7
}
{
"_id" : 89.9
}
{
"_id" : 94.15
}
{
"_id" : 102.1
}
I need the contents of the ID fields from all 7 documents in an array of values in one document:
{values: [254.8, 93.7, 89.9, 94.15, 102.1, 93.9, 102.7]}
It would be easy to sort this with JS once I have the results but I'd rather do it in the pipeline if possible so my JS stays 100% generic and only returns pure pipeline data.
Here is what you need to complete the job:
db.collection.aggregate([
{
"$group": {
"_id": null,
"values": {
$push: "$_id"
}
}
},
{
"$project": {
_id: false
}
}
])
The result will be:
[
{
"values": [
254.8,
93.7,
89.9,
94.15,
102.1,
93.9,
102.7
]
}
]
https://mongoplayground.net/p/pTmR_rni0J1

Mongo Query Filter $lookup not filtering correctly

I have 3 query's that as far as my knowledge goes should be synonymous, But they give very different results, And I am wondering if someone could help fill in my knowledge gap on this.
This one works perfect, and gives the exact result I am looking for, The user who created it
db.getCollection("core-navlink").aggregate([
{ $match : { id : "c1a13efc-1203-436b-a32c-e59a889c08a3" } },
{
$lookup : {
from : "core-user",
as : "created_by_user",
let : { module_id : "$created_by.module_id" },
pipeline : [
{ $match : { $expr : { $and : [ { $eq : [ "$id" , "$$module_id"] } ] } } }
]
}
}
]).projection({})
.sort({_id:-1})
.limit(100)
This one, "created_by_user" gives all user's not just the one I am looking for
db.getCollection("core-navlink").aggregate([
{ $match : { id : "c1a13efc-1203-436b-a32c-e59a889c08a3" } },
{
$lookup : {
from : "core-user",
as : "created_by_user",
let : { module_id : "$created_by.module_id" },
pipeline : [
{ $match : { $expr : { $and : [ { id : "$$module_id" } ] } } }
]
}
}
]).projection({})
.sort({_id:-1})
.limit(100)
And this one gives no results back in the created_by_user array
db.getCollection("core-navlink").aggregate([
{ $match : { id : "c1a13efc-1203-436b-a32c-e59a889c08a3" } },
{
$lookup : {
from : "core-user",
as : "created_by_user",
let : { module_id : "$created_by.module_id" },
pipeline : [
{ $match : { id : "$$module_id" } }
]
}
}
]).projection({})
.sort({_id:-1})
.limit(100)
UPDATE : Example Documents
core-navlink: ( Minimized to only relevent fields )
{
"_id": "c1a13efc-1203-436b-a32c-e59a889c08a3",
"id": "c1a13efc-1203-436b-a32c-e59a889c08a3",
"created_by": {
"module_name": "USER",
"module_id": "f0ae108f-7dca-4bd1-ba7a-bcf50d9f7814"
}
}
core-user
{
"_id": "f0ae108f-7dca-4bd1-ba7a-bcf50d9f7814",
"id": "f0ae108f-7dca-4bd1-ba7a-bcf50d9f7814",
"first_name": "Joe",
"last_name": "User",
}
Lookup doesn't work with pipeline attribute.
Please Correct me if assumes field name wrong.
Try as below:
db.getCollection("core-navlink").aggregate([
{ $match : { id : "c1a13efc-1203-436b-a32c-e59a889c08a3" } },
{
$lookup : {
from : "core-user",
localField: "module_id", // assumes this field on core-navlink
foreignField: "_id", // assumes this field on "core-user"
as : "created_by_user"
}
}
]);

Mongo query for date range in ISODate format

I'm trying to create this query to use with jasper reports but something is wrong with my syntax, i have 2 ISODates in my db and i want to query between then, in this query I use $match and $gt, $lt.
Anyway something is not correct because the console says error but doesn`t specify it.
Obs: The problem is something with the $match and the $lt and $gt. because without them it is possible to query.
Code:
{
runCommand : {
aggregate : 'saleCalculation',
pipeline : [
{
$match: {
processingInfo.modifiedDate: {$gt : ISODate("2016-05-01T09:29:40.572Z")},
{$lt : ISODate("2016-06-01T09:29:40.572Z")}
}
},
{
$project : {
header.documentCode : 1,
header.transactionDate : 1,
header.metadata.entityName : 1,
lines : 1
}
},
{ $unwind : '$lines'},
{ $unwind :'$lines.calculatedTax.details' }
]
}
}
Jasper Manual says i need to do something like that:
http://community.jaspersoft.com/wiki/how-query-mongo-isodate-data-parameter
but i didn't understand that
Try this code :
{
runCommand : {
aggregate : 'saleCalculation',
pipeline : [
{
$match: {
processingInfo.modifiedDate: {$gt : ISODate("2016-05-01T09:29:40.572Z"),
$lt : ISODate("2016-06-01T09:29:40.572Z")}
}
},
{
$project : {
header.documentCode : 1,
header.transactionDate : 1,
header.metadata.entityName : 1,
lines : 1
}
},
{ $unwind : '$lines'},
{ $unwind :'$lines.calculatedTax.details' }
]
}
}
Try with this pipeline. Make sure to use double quotes.
[
{ "$match": {"processingInfo.modifiedDate": { "$gt":ISODate("2010-03-26T18:40:59.000Z"), "$lt":ISODate("2016-09-28T18:40:59.000Z")}}},
{
"$project" : {
"header.documentCode": 1,
"header.transactionDate": 1,
"header.metadata.entityName": 1,
"lines" : 1
}
},
{ "$unwind": "$lines"},
{ "$unwind": "$lines.calculatedTax.details" }
]
I tried this command on mongo shell and it worked without any error.
db.runCommand({"aggregate": "saleCalculation", "pipeline" : [
{ "$match": {"processingInfo.modifiedDate": { "$gt":ISODate("2010-03-26T18:40:59.000Z"), "$lt":ISODate("2016-09-28T18:40:59.000Z")}}},
{
"$project" : {
"header.documentCode": 1,
"header.transactionDate": 1,
"header.metadata.entityName": 1,
"lines" : 1
}
},
{ "$unwind": "$lines"},
{ "$unwind": "$lines.calculatedTax.details" }
]
})

Mongodb $nin querying sub-document

Here is the sample document of my MongoDB:
user: {
_id:1,
name:'xyz',
age:12,
mobile:21321312,
transaction:[
{
trans_id:1,
prod:'a',
purchasedAt:ISODate("2015-02-01"),
},
{
trans_id:2,
prod:'b',
purchasedAt:ISODate("2015-02-01")
},
{
trans_id:3,
prod:'c',
purchasedAt:ISODate("2014-11-24")
}
]
}
I want to get the users who have purchased product 'a' on date '2015-02-01' but not have purchased the product 'b' or 'c' on same day. So I tried querying:
db.user.find({transaction:{$elemMatch:{prod:'a',purchasedAt:ISODate("2015-02-01")}}, transaction:{$elemMatch:{prod:{$nin:['b', 'c']}, purchasedAt:ISODate("2015-02-01")}}})
But the query seems to return wrong result. It contains some users who have purchased product 'c' on the same day. So I tried:
db.user.find({transaction:{$elemMatch:{prod:{$in:['a'], $nin:['b','c']}, purchasedAt:ISODate("2015-02-01")}}})
db.user.find({$and:[{transaction:{$elemMatch:{prod:'a',purchasedAt:ISODate("2015-02-01")}}}, {transaction:{$elemMatch:{prod:{$nin:['b', 'c']}, purchasedAt:ISODate("2015-02-01")}}}]})
But none seems to work. I always get the product with same purchased date which is in $nin part. I have tried other queries also but those are of same kind as above (like querying with dot '.' operator) and are trivial to mention here. Is there any way to get the result I want?
Try this:
db.test.find({
"$and" : [
{ "transaction" : {
"$elemMatch" : { "prod" : "a", "purchasedAt" : ISODate("2015-02-01") }
} },
{ "transaction" : { "$not" : {
"$elemMatch" : { "prod" : "b", "purchasedAt" : ISODate("2015-02-01") }
} } },
{ "transaction" : { "$not" : {
"$elemMatch" : { "prod" : "c", "purchasedAt" : ISODate("2015-02-01") }
} } }
]
})
Following two ways you can find out your results
1> Using mongo aggregation
db.collectionName.aggregate({
"$unwind": "$user.transaction"
}, {
"$match": {
"$and": [{
"user.transaction.prod": "a"
}, {
"user.transaction.prod": {
"$nin": ["b", "c"]
}
}, {
"user.transaction.purchasedAt": ISODate("2015-02-01T00:00:00Z")
}]
}
}).pretty()
2> Using find with projection
db.collectionName.find({
"user.transaction.prod": "a",
"user.transaction.purchasedAt": ISODate("2015-02-01T00:00:00Z")
},
{
"user.transaction.$": 1
}).pretty()

Mongodb count() of internal array

I have the following MongoDB collection db.students:
/* 0 */
{
"id" : "0000",
"name" : "John"
"subjects" : [
{
"professor" : "Smith",
"day" : "Monday"
},
{
"professor" : "Smith",
"day" : "Tuesday"
}
]
}
/* 1 */
{
"id" : "0001",
"name" : "Mike"
"subjects" : [
{
"professor" : "Smith",
"day" : "Monday"
}
]
}
I want to find the number of subjects for a given student. I have a query:
db.students.find({'id':'0000'})
that will return the student document. How do I find the count for 'subjects'? Is it doable in a simple query?
If query will return just one element :
db.students.find({'id':'0000'})[0].subjects.length;
For multiple elements in cursor :
db.students.find({'id':'0000'}).forEach(function(doc) {
print(doc.subjects.length);
})
Do not forget to check existence of subjects either in query or before check .length
You could use the aggregation framework
db.students.aggregate(
[
{ $match : {'_id': '0000'}},
{ $unwind : "$subjects" },
{ $group : { _id : null, number : { $sum : 1 } } }
]
);
The $match stage will filter based on the student's _id
The $unwind stage will deconstruct your subjects array to multiple documents
The $group stage is when the count is done. _id is null because you are doing the count for only one user and only need to count.
You will have a result like :
{ "result" : [ { "_id" : null, "number" : 187 } ], "ok" : 1 }
Just another nice and simple aggregation solution:
db.students.aggregate([
{ $match : { 'id':'0000' } },
{ $project: {
subjectsCount: { $cond: {
if: { $isArray: "$subjects" },
then: { $size: "$subjects" },
else: 0
}
}
}
}
]).then(result => {
// handle result
}).catch(err => {
throw err;
});
Thanks!