Mongoose, filter subdocument array by date - mongodb

[{
"_id" : ObjectId("5f3d0f13fd6fd6667f8f56d6"),
"name" : "A",
"prices" : [
{
"_id" : ObjectId("5f3d0f16fd6fd6667f8f57fb"),
"d" : ISODate("2019-08-19T00:00:00.000Z"),
"h" : 182.1,
},
{
"_id" : ObjectId("5f3d0f16fd6fd6667f8f57fc"),
"d" : ISODate("2019-08-20T00:00:00.000Z"),
"h" : 182.1,
},
{
"_id" : ObjectId("5f3d0f16fd6fd6667f81f57fc"),
"d" : ISODate("2019-08-21T00:00:00.000Z"),
"h" : 182.1,
}
]
}]
Input:
from: '2019-08-20'
to: '2019-08-21'
Exepected output
[{
"_id" : ObjectId("5f3d0f13fd6fd6667f8f56d6"),
"name" : "A",
"prices" : [
{
"_id" : ObjectId("5f3d0f16fd6fd6667f8f57fc"),
"d" : ISODate("2019-08-20T00:00:00.000Z"),
"h" : 182.1,
},
{
"_id" : ObjectId("5f3d0f16fd6fd6667f81f57fc"),
"d" : ISODate("2019-08-21T00:00:00.000Z"),
"h" : 182.1,
}
]
}]
So I want to filter prices array so it only returns items within the given date range based on variable d
So some form of aggregation.
mongoose.model("stock").aggregate(...)
Some combination of $unwind $filter, $gte, $gle

You can do as below
db.collection.aggregate([
{
$project: {
items: {
$filter: {
input: "$prices",
as: "price",
cond: {
"$and": [//only date conditions
{
$gte: [
"$$price.d",
new Date("2019-08-20")
]
},
{
$lte: [
"$$price.d",
new Date("2019-08-21")
]
}
]
}
}
}
}
}
])
play

Related

How to get the recent values of grouped result?

Below is the document which has an array name datum and I want to filter the records based on StatusCode, group by Year and sum the amount value from the recent record of distinct Types.
{
"_id" : ObjectId("5fce46ca6ac9808276dfeb8c"),
"year" : 2018,
"datum" : [
{
"StatusCode" : "A",
"Type" : "1",
"Amount" : NumberDecimal("100"),
"Date" : ISODate("2018-05-30T00:46:12.784Z")
},
{
"StatusCode" : "A",
"Type" : "1",
"Amount" : NumberDecimal("300"),
"Date" : ISODate("2023-05-30T00:46:12.784Z")
},
{
"StatusCode" : "A",
"Type" : "2",
"Amount" : NumberDecimal("420"),
"Date" : ISODate("2032-05-30T00:46:12.784Z")
},
{
"StatusCode" : "B",
"Type" : "2",
"Amount" : NumberDecimal("420"),
"Date" : ISODate("2032-05-30T00:46:12.784Z")
}
]
}
In my case following is the expected result :
{
Total : 720
}
I want to achieve the result in the following aggregate Query pattern
db.collection.aggregate([
{
$addFields: {
datum: {
$reduce: {
input: "$datum",
initialValue: {},
"in": {
$cond: [
{
$and: [
{ $in: ["$$this.StatusCode", ["A"]] }
]
},
"$$this",
"$$value"
]
}
}
}
}
},
{
$group: {
_id: "$year",
RecentValue: { $sum: "$datum.Amount" }
}
}
])
You can first $unwind the datum array. Do the filtering and sort by the date. Then get the record with latest datum by a $group. Finally do another $group to calculate the sum.
Here is a mongo playground for your reference.

MongoDB how to select only specific sub_elements for a document?

In MongoDB I have documents like this:
{
"_id" : ObjectId("5cc9f3c87aa1024e079a3abf"),
"created_at" : ISODate("2019-04-01T00:00:00.000Z"),
"demographics" : [
{
"key" : "gender",
"value" : "male"
},
{
"key" : "birth_year",
"value" : 1992
},
{
"key" : "city_or_rural",
"value" : "rural"
},
{
"key" : "car_purchase_intention",
"value" : "no"
},
{
"key" : "education_level",
"value" : "high"
},
{
"key" : "age",
"value" : 26
}
]
}
And I would like to make a query by "created_at" but for each document in the result only returning the demographics elements which key are "age" or "gender".
I am trying combinations of $unwind and $project but I can not obtain any proper result.
I am expecting results as this:
# 1
{
"_id" : ObjectId("5cc9f3c87aa1024e079a3abf"),
"created_at" : ISODate("2019-04-01T00:00:00.000Z"),
"demographics" : [
{
"key" : "gender",
"value" : "male"
},
{
"key" : "age",
"value" : 26
}
]
}
# 2
{
"_id" : ObjectId("5cc9f3c87aa1024e079axxx"),
"created_at" : ISODate("2019-04-01T00:00:00.000Z"),
"demographics" : [
{
"key" : "gender",
"value" : "female"
},
{
"key" : "age",
"value" : 56
}
]
}
You can use $filter aggregation after you match by created_at field.
$filter selects a subset of an array to return based on the specified condition and returns an array with only those elements that match the condition.
db.collection.aggregate([
{
$match: {
created_at: ISODate("2019-04-01T00:00:00.000Z")
}
},
{
$project: {
created_at: "$created_at",
demographics: {
$filter: {
input: "$demographics",
as: "item",
cond: {
$in: [
"$$item.key",
[
"gender",
"age"
]
]
}
}
}
}
}
])
Playground

How to find the records with same key value assigned to multiple values in MongoDB

I have data like the following,
Student | Subject
A | Language
A | Math
B | Science
A | Arts
C | Biology
B | History
and so on...
I want to fetch the students who has same name but enrolled in two different subjects Language & Math only.
I tried to use the query:
$group:{
_id:"$student",
sub:"{$addToSet:"$subject"}
},
$match:{
sub:{$in:["Language","Math"]}
}
But I am getting no documents to preview in MongoDB Compass. I am working in a VM machine, Compass is able to group only biology, history, science, arts only but not able to group language and math. I wanted to get A as my output.
Thanks in loads.
The collection data and the expected output:
{ Student:"A", Subject:"Language" },
{ Student:"A", Subject:"Math" },
{ Student:"B", Subject:"Science" },
{ Student:"A", Subject:"Arts" },
{ Student:"C", Subject:"Biology" },
{ Student:"B", Subject:"History" }
I am looking to get A as my output.
You are almost there, just need some tweak to your aggregation pipeline:
const pipeline = [
{
$group:
{
_id: '$Student', // Group students by name
subjects: {
$addToSet: '$Subject', // Push all the subjects they take uniquely into an array
},
},
},
{
// Filter for students who only offer Language and Mathematics
$match: { subjects: { $all: ['Language', 'Math'], $size: 2 } },
},
];
db.students.aggregate(pipeline);
That should give an output array like this:
[
{ "_id" : studentName1 , "subjects" : [ "Language", "Math" ] },
{ "_id" : studentName2 , "subjects" : [ "Language", "Math" ] },
....
]
You have to use an Aggregation operator, $setIsSubset. The $in (aggregation) operator is used to check an array for one value only. I think you are thinking of $in (query operator)..
The Query:
db.student_subjects.aggregate( [
{ $group: {
_id: "$student",
studentSubjects: { $addToSet: "$subject" }
}
},
{ $project: {
subjectMatches: { $setIsSubset: [ [ "Language", "Math" ], "$studentSubjects" ] }
}
},
{ $match: {
subjectMatches: true
}
},
{ $project: {
matched_student: "$_id", _id: 0
}
}
] )
The Result:
{ "matched_student" : "A" }
NOTES:
If you replace [ "Language", "Math" ] with [ "History" ], you will get the result: { "matched_student" : "B" }.
You can also try and see other set operators (aggregation), like the $allElementsTrue. Use the best one that suits your application.
[ EDIT ADD ]
Sample data for student_subjects collection:
{ "_id" : 1, "student" : "A", "subject" : "Language" }
{ "_id" : 2, "student" : "A", "subject" : "Math" }
{ "_id" : 3, "student" : "B", "subject" : "Science" }
{ "_id" : 4, "student" : "A", "subject" : "Arts" }
{ "_id" : 5, "student" : "C", "subject" : "Biology" }
{ "_id" : 6, "student" : "B", "subject" : "History" }
The Result After Each Stage:
1st Stage: $group
{ "_id" : "C", "studentSubjects" : [ "Biology" ] }
{ "_id" : "B", "studentSubjects" : [ "History", "Science" ] }
{ "_id" : "A", "studentSubjects" : [ "Arts", "Math", "Language" ] }
2nd Stage: $project
{ "_id" : "C", "subjectMatches" : false }
{ "_id" : "B", "subjectMatches" : false }
{ "_id" : "A", "subjectMatches" : true }
3rd Stage: $match
{ "_id" : "A", "subjectMatches" : true }
4th Stage: $project
{ "matched_student" : "A" }

Mongodb Aggregation orderless items in Group _id

I am storing messages between users in a collection with this schema:
{
"_id" : ObjectId("5b23c455e3fce278f9e8d05f"), //message id
"f" : ObjectId("5ad13aaa1ba073601cc16bca"), //sender userid
"c" : "Hi", //contents
"t" : ObjectId("5ad2de5c691a4008cf6923b4"), //reciever userid
}
I am trying to query db to generate a list of current user conversations just like whatsapp list with the last message embedded using this aggregation:
db.getCollection('message').aggregate(
[
{ $match: { $or: [ { f: ObjectId("5ad13aaa1ba073601cc16bca") }, {t:ObjectId("5ad13aaa1ba073601cc16bca")} ] } },
{
$group : {
_id :{f:"$f",t:"$t"},
c: { $push: "$$ROOT" }
}
}
]
)
Result is:
{
"_id" : {
"f" : ObjectId("5ad13aaa1ba073601cc16bca"),
"t" : ObjectId("5ad2de5c691a4008cf6923b4")
},
"c" : [
{
"_id" : ObjectId("5b23c455e3fce278f9e8d05f"),
"f" : ObjectId("5ad13aaa1ba073601cc16bca"),
"c" : "Hi",
"t" : ObjectId("5ad2de5c691a4008cf6923b4"),
"d" : ISODate("2018-06-15T13:48:34.000Z"),
}
]
},
{
"_id" : {
"f" : ObjectId("5ad2de5c691a4008cf6923b4"),
"t" : ObjectId("5ad13aaa1ba073601cc16bca")
},
"c" : [
{
"_id" : ObjectId("5b235fea43966a767d2d9604"),
"f" : ObjectId("5ad2de5c691a4008cf6923b4"),
"c" : "Hello",
"t" : ObjectId("5ad13aaa1ba073601cc16bca"),
"d" : ISODate("2018-06-15T06:40:07.000Z"),
}
]
}
As you can see, there is a conversation between 5ad13aaa1ba073601cc16bca and 5ad2de5c691a4008cf6923b4. The group acts on f and t with their order. But we do just need to find conversations regardless of the order of f and t. Thus, the result document should be just like this with the latest message embedded:
{
"_id" : {
"x" : ObjectId("5ad13aaa1ba073601cc16bca"),
"y" : ObjectId("5ad2de5c691a4008cf6923b4")
},
"c" : [
{
"_id" : ObjectId("5b23c455e3fce278f9e8d05f"),
"f" : ObjectId("5ad13aaa1ba073601cc16bca"),
"c" : "Hi",
"t" : ObjectId("5ad2de5c691a4008cf6923b4"),
"d" : ISODate("2018-06-15T13:48:34.000Z"),
}
]
}
How can I handle this with aggregation? Any suggestions? Thanks.
Of course, you can handle it with an aggregation! As your _ids are ObjectIds, you can compare them with relational operators, therefore you can use $max and $min on them:
db.getCollection('message').aggregate([
{$match: {$or: [{f: _id}, {t: _id}]}},
{$group: {
_id: {
x: {$max: ['$f', '$t']},
y: {$min: ['$f', '$t']}
},
c: {$push: '$$ROOT'}}
}
])

How to compare array Field with Array input value in $filter mongodb

I willing to apply $filter in this collection to get only "Applied Mathematics" result only in array. I am providing input in form of array like this ["Applied Mathematics"] to below collection.
{
"_id" : ObjectId("58a95d81eead32e82932b535"),
"univisorEducation" : [
{
"educationLevel" : "BACHELOR",
"courseType" : "UNDERGRADUATE",
"year" : 2016,
"college" : ObjectId("58a936add48f2b502858a70d"),
"_id" : ObjectId("58a95eb2eead32e82932b541"),
"course" : [
"Anthropology",
"Biomedical Engineering",
"Applied Mathematics"
]
}
]
}
{
"_id" : ObjectId("58a9643deead32e82932b54b"),
"univisorEducation" : [
{
"educationLevel" : "DOCTORATE",
"courseType" : "GRADUATE",
"year" : 2020,
"college" : ObjectId("58a936afd48f2b502858b5e2"),
"_id" : ObjectId("58a96495eead32e82932b550"),
"course" : [
"Applied Mathematics",
"Applied Physics"
]
},
{
"educationLevel" : "MASTER",
"courseType" : "GRADUATE",
"year" : 2020,
"college" : ObjectId("58a936afd48f2b502858b9f7"),
"_id" : ObjectId("58a96495eead32e82932b54f"),
"course" : [
"Applied Mathematics"
]
}
]
}
For this purpose i am using a $filter in $project, below is my $filter code
$filter: {
input: "$univisorEducation",
as: "univisorEducation",
cond: {
$setIsSubset: ["$$univisorEducation.course", courses]
}
}
Issue is that i am only getting this result
{ "_id" : ObjectId("58a95d81eead32e82932b535"), "univisorEducation" : [ ] }
{
"_id" : ObjectId("58a9643deead32e82932b54b"),
"univisorEducation" : [
{
"educationLevel" : "MASTER",
"courseType" : "GRADUATE",
"year" : 2020,
"college" : ObjectId("58a936afd48f2b502858b9f7"),
"_id" : ObjectId("58a96495eead32e82932b54f"),
"course" : [
"Applied Mathematics"
]
}
]
}
The collection with "_id" : ObjectId("58a95d81eead32e82932b535") should have result with Applied Mathematics.
Change your $filter aggregation to below.
Compares courses array to univisorEducation.course and returns true there is match univisorEducation.course or false otherwise.
$filter: {
input: "$univisorEducation",
as: "univisorEducation",
cond: {
$ne:[{$setIntersection: [courses, "$$univisorEducation.course" ]}, []]
}
}