mongodb aggregation with filter options - mongodb

I have two collections where I'm trying to do an aggregation query with filter options. I have looked online but I couldn't find solution for this.
Col 1
[
{
_id: ObjectId('st_123'),
stud_num: 123,
school: ObjectId('sc_123'),
gender: 'M'
},
{
_id: ObjectId('st_234'),
stud_num: 123,
school: ObjectId('sc_234'),
gender: 'F'
},
{
_id: ObjectId('st_345'),
stud_num: 123,
school: ObjectId('sc_345'),
gender: 'M'
}
]
Col 2
[
{
_id: ObjectId('f_123'),
stud_health_id: ObjectId('st_123'),
schoolYear: ObjectId('sy123')
},
{
_id: ObjectId('f_234'),
stud_health_id: ObjectId('st_234'),
schoolYear: ObjectId('sy234')
},
{
_id: ObjectId('f_345'),
stud_health_id: ObjectId('st_890'),
schoolYear: ObjectId('sy234')
},
{
_id: ObjectId('f_456'),
stud_health_id: ObjectId('st_345'),
schoolYear: ObjectId('sy345')
}
]
I am trying to filter the records from collection 1 which doesn't have entry in collection 2 with extra params.
If I send {schoolYear: ObjectID('sy234)} then it should return the first and third document of collection 1 because for that year those two students doesn't have record.

One option is using $lookup and $match:
db.col1.aggregate([
{$lookup: {
from: "col2",
as: "col2",
let: {schoolYear: "sy234", stud_id: "$_id"},
pipeline: [
{$match: {$expr: {
$and: [
{$eq: ["$schoolYear", "$$schoolYear"]},
{$eq: ["$stud_health_id", "$$stud_id"]}
]
}
}
}
]
}
},
{$match: {"col2.0": {$exists: false}}},
{$unset: "col2"}
])
See how it works on the playground example

Related

how to use $match after $group in mongodb aggregation

I have 4 products. I want to know the count of product-4 for users who has product-1 or product-2
Sample data:
[
{
"user_id": 1,
"product_type": "product-1"
},
{
"user_id": 1,
"product_type": "product-4"
},
{
"user_id": 1,
"product_type": "product-4"
},
{
"user_id": 2,
"product_type": "product-1"
}
]
user-1 has two product-4 and one product-1 (that counts 2)
user-2 has only product-1, but no product-4 (hence that does not count)
This is how I tried
db.collection.aggregate([
{
$match: {
product_type: {
$in: [
"product-1​",
"product-2",
],
},
},
},
{
$group: {
_id: "$user_id",
},
},
{
$match: {
user_id: { $in: "$_id"}, // I want to use $group's result in here
product_type: "product-4",
},
}
]);
Expected results are:
[
{
"_id": 1,
"count": 2
},
{
"_id": 2,
"count": 0
}
]
Note:
I dont have a backend, I have to this using mongodb only.
Does this answer your question?
db.collection.aggregate([
{$group: {_id: "$user_id", data: {$push: "$product_type"}}},
{$match: {$expr: {$or: [
{$in: ["product-1", "$data"]},
{$in: ["product-2", "$data"]}
]}}},
{$project: {
count: {
$size: {
$filter: {
input: "$data",
cond: {$eq: ["$$this", "product-4"]}
}
}
}
}}
])
See how it works on the playground example

MongoDB Aggregation with Sum and Multiple Group Results

Let's say I have these collections members and positions
[
{
"church":"60dbb265a75a610d90b45c6b", "parentId":"60dbb265a75a610d90b45c6b", name: "Jonah John", status: 1, birth: "1983-01-01", position: "60f56f59-08be-49ec-814a-2a421f21bc08"
},
{
"church":"60dbb265a75a610d90b45c6b", "parentId":"60dbb265a75a610d90b45c6b", name: "March John", status: 1, birth: "1981-01-23", position: "60f56f59-08be-49ec-814a-2a421f21bc08"
},
{
"church":"60dbb265a75a610d90b45c6b", "parentId":"60dbb265a75a610d90b45c6b",name: "Jessy John", status: 0, birth: "1984-08-01", position: "e5bba609-082c-435a-94e3-0997fd229851"
}
]
[
{_id: "60f56f59-08be-49ec-814a-2a421f21bc08", name: "Receptionist"},
{_id: "5c78ba5a-3e6c-4d74-8d4a-fa23d02b8003", name: "Curtain"},
{_id: "e5bba609-082c-435a-94e3-0997fd229851", name: "Doorman"}
]
I want to aggregate in a way I can get:
inactiveMembers
activeMembers
totalMembers
totalPositionsOcuppied
And two arrays with:
positionsOcuppied {name, quantity}
birthdays {month, quantity.
I need an output like this:
{
"_id": {
"church":"60dbb265a75a610d90b45c6b",
"parentId":"60dbb265a75a610d90b45c6b"
},
"inactiveMembers":1,
"activeMembers":2,
"totalMembers":3,
"birthdays": [
{january:2}, {august:1}
],
"positionsOcuppied": [
{Doorman: 1}, {Receptionist:2}
],
"totalPositionsOcuppied": 3
}
How can I do that?
PS.: Very sorry for unclear values...
Update:
$addFields with birthMonth string
$lookup to add positions
$facet to $group by birthdays, positionsOcuppied, and all docs tougher as other
$map to format birthdays and positionsOcuppied
Format the answer
db.people.aggregate([
{$addFields: {
birthMonth: {
$arrayElemAt: [
["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
{$month: {$toDate: "$birth"}}
]
}
}
},
{$lookup: {from: "positions", localField: "position", foreignField: "_id",
as: "position"}},
{$facet: {
birthdays: [{$group: {_id: "$birthMonth", count: {$sum: 1}}}],
positionsOcuppied: [{$group: {_id: {$first: "$position.name"}, count: {$sum: 1}}}],
other: [
{$group: {_id: 0,
activeMembers: {$sum: "$status"},
totalMembers: {$sum: 1},
church: {$first: "$church"},
parentId: {$first: "$parentId"},
totalPositionsOcuppied: {$sum: {$size: "$position"}}
}
}
]
}
},
{$set: {
birthdays: {
$map: {input: "$birthdays", in: [{k: "$$this._id", v: "$$this.count"}]}
},
positionsOcuppied: {
$map: {input: "$positionsOcuppied", in: [{k: "$$this._id", v: "$$this.count"}]}
},
other: {$first: "$other"}
}
},
{$set: {
"other.birthdays": {
$map: {input: "$birthdays", in: {$arrayToObject: "$$this"}}
},
"other.positionsOcuppied": {
$map: {input: "$positionsOcuppied", in: {$arrayToObject: "$$this"}}
},
"other.inactiveMembers": {
$subtract: ["$other.totalMembers", "$other.activeMembers"]
},
"other._id": {church: "$other.church", parentId: "$other.parentId"},
birthdays: "$$REMOVE",
"other.church": "$$REMOVE",
"other.parentId": "$$REMOVE",
positionsOcuppied: "$$REMOVE"
}
},
{$replaceRoot: {newRoot: "$other"}}
])
See how it works on the playground example

MongoDB aggregation grouping fill missing values

I'm using MongoDB aggregation framework. I have a Mongo collection with documents like this:
{
'step': 1,
'name': 'house',
'score': 2
}
{
'step': 1,
'name': 'car',
'score': 3
}
{
'step': 2,
'name': 'house',
'score': 4
}
I'm grouping the documents with same 'step' and pushing 'name' and 'score' into an array of objects. What I get is:
{
'step': 1,
'scores':
[
{'name':'house','score':2},
{'name':'car','score':3}
]
}
{
'step': 2,
'scores':
[
{'name':'house','score':4}
]
}
For each 'step' I need to copy the value of previous 'step' in case that a 'name' does not exists. I should have something like this:
{
'step': 1,
'scores':
[
{'name':'house','score':2},
{'name':'car','score':3}
]
}
{
'step': 2,
'scores':
[
{'name':'house','score':4},
**{'name': 'car', 'score':3}**
]
}
At the second document the element {'name':'car','score':3} has been copied from the previous document because at 'step:2' there is not documents having 'score' for 'car'.
I'm not able to figure out how to do this operation with MongoDB aggregation. Some help will be very appreciated.
Required to use $lookup with pipeline, look below step by step,
$group by step and push all scores in one array scores
push all name in names of each score of particular step, we will use in match condition inside lookup
db.collection.aggregate([
{
$group: {
_id: "$step",
scores: {
$push: {
name: "$name",
score: "$score"
}
},
names: { $push: "$name" }
}
},
$unwind scores because its array and we are going to lookup
{ $unwind: "$scores" },
$lookup let variables step(_id) and names for pipeline level
$match condition with expression $expr there are 3 conditions
check the size of names It should be one(1), either its car or house,
match step number, it should be equal
match not in for ex. if car is already available then it will search for house in lookup, need to use separate $not and than $in
$project to show required fields
lookup result will store in clone_score
{
$lookup: {
from: "collection",
let: {
step_id: { $subtract: ["$_id", 1] },
names: "$names"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: [{ $size: "$$names" }, 1] },
{ $eq: ["$$step_id", "$step"] },
{ $not: [{ $in: ["$name", "$$names"] }] }
]
}
}
},
{
$project: {
_id: 0,
name: 1,
score: 1
}
}
],
as: "clone_score"
}
},
$group by step(_id) and push all scores in one array scores, keep first clone_score
{
$group: {
_id: "$_id",
scores: { $push: "$scores" },
clone_score: { $first: "$clone_score" }
}
},
from above pipelines, we have two separate array scores and clone_score now,
$project we need to concat both of them in scores
{
$project: {
_id: 0,
step: "$_id",
scores: {
$concatArrays: ["$scores", "$clone_score"]
}
}
}
])
Playground: https://mongoplayground.net/p/Fytf7NEU7uG

MongoDb >4.2 Using aggregation pipeline in updateMany gives $match is not allowed to be used within an update

I read that starting with MongoDB 4.2 you can do aggregation pipeline with updateMany. I don't have a good gasp of aggregation pipeline so I thought of doing following to my collection.
user{
id: ObjectId(..),
books: [
{
value: 10,
type: PDF
},
{
value: 10,
type: HARDCOVER
}
...
]
}
I am trying to lower the value of PDF book from 10 to 5 if user has PAPER_BACK or HARDCOVER in their books
db.user.updateMany(
{},
[
{$match: {'books' : { $elemMatch: {'value': '10', 'type': 'PDF'}}, 'books.type': {$in: ["PAPER_BACK", "HARDCOVER"]}}},
{$unwind: '$books'},
{$match: {"books.type": "PDF"}},
{$set: {"books.value": "5"}}
]
)
I thought the above work with the new updateMany() but it's throwing error saying $match is not allowed to be used. Am I misunderstanding completely?
As you noted $match can't be used within the pipe, nor can $unwind. Use the first arg of updateMany to perform your match.
You are querying the string '10', make sure you change to a number.
{
books: {
$elemMatch: {
"value": 10,
"type": "PDF"
}
},
"books.type": {
$in: [
"HARDCOVER",
"SOFTCOVER"
]
}
}
Then to perform the update, use can use $map like this:
db.user.updateMany(
{
books: {
$elemMatch: {
"value": 10,
"type": "PDF"
}
},
"books.type": {
$in: [
"HARDCOVER",
"SOFTCOVER"
]
}
},
[
{$set: {
books: {$map: {
input: "$books",
in: {$mergeObjects: [
"$$this",
{$cond: [
{$and: [
{$eq: ["$$this.type", "PDF"]},
{$eq: ["$$this.value", 10]}
]},
{value: 5},
null
]}
]}
}}
}}
]
)

Mongodb Aggregation count array/set size

Here's my problem:
Model:
{ application: "abc", date: Time.now, status: "1" user_id: [ id1, id2,
id4] }
{ application: "abc", date: Time.yesterday, status: "1", user_id: [
id1, id3, id5] }
{ application: "abc", date: Time.yesterday-1, status: "1", user_id: [
id1, id3, id5] }
I need to count the unique number of user_ids in a period of time.
Expected result:
{ application: "abc", status: "1", unique_id_count: 5 }
I'm currently using the aggregation framework and counting the ids outside mongodb.
{ $match: { application: "abc" } }, { $unwind: "$users" }, { $group:
{ _id: { status: "$status"},
users: { $addToSet: "$users" } } }
My arrays of users ids are very large, so I have to iterate the dates or I'll get the maximum document limit (16mb).
I could also $group by
{ year: { $year: "$date" }, month: { $month: "$date" }, day: {
$dayOfMonth: "$date" }
but I also get the document size limitation.
Is it possible to count the set size in mongodb?
thanks
The following will return number of uniqueUsers per application. This will apply an group operation to a result of a group operation by using pipeline feature of mongodb.
{ $match: { application: "abc" } },
{ $unwind: "$users" },
{ $group: { _id: "$status", users: { $addToSet: "$users" } } },
{ $unwind:"$users" },
{ $group : {_id : "$_id", count : {$sum : 1} } }
Hopefully this will be done in an easier way in the following releases of mongo by a command which gives the size of an array under a projection. {$project: {id: "$_id", count: {$size: "$uniqueUsers"}}}
https://jira.mongodb.org/browse/SERVER-4899
Cheers
Sorry I'm a little late to the party. Simply grouping on the 'user_id' and counting the result with a trivial group works just fine and doesn't run into doc size limits.
[
{$match: {application: 'abc', date: {$gte: startDate, $lte: endDate}}},
{$unwind: '$user_id'},
{$group: {_id: '$user_id'}},
{$group: {_id: 'singleton', count: {$sum: 1}}}
];
Use $size to get the size of set.
[
{
$match: {"application": "abc"}
},
{
$unwind: "$user_id"
},
{
$group: {
"_id": "$status",
"application": "$application",
"unique_user_id": {$addToSet: "$user_id"}
}
},
{
$project:{
"_id": "$_id",
"application": "$application",
"count": {$size: "$unique_user_id"}
}
}
]