For the below dataset I want to receive the documents in such a way that if the user has the role of manager and it is active then I want to ignore all the other records of that user with the role user but if manager with active false then I want all the records with role user it doesn't matter if it is active or not
2.If the record with role manager doesn't exist I want all the role user records for that user and vice versa
[
0:{
role:"manager",
user:"john",
active:true
},
1:{
role:"user",
region:"us",
user:"john",
active:false
},
2:{
role:"user",
region:"czk",
user:"john",
active:false
},
3:{
role:"user",
region:"czk",
user:"jane",
active:false
},
4:{
role:"user",
region:"us",
user:"jane",
active:true
},
5:{
role:"manager",
user:"jane",
active:false
},
]
So in Above case index 0 and 3,4 will be fetched from database
So far for achieving this output i'd tried this
db.collection.aggregate([
{
$match:{
$or:[
{ $and: [{ role: manager }, { active: true }]},
{ $and: [{ role: user }, { active: true }] }
]
}
}
])
but it does not give the expected output
I am afraid this is not as straightforward. You will have to first group the documents by username, and then filter out the grouped documents array, according to your criteria. This is one way of doing it:
db.collection.aggregate([
{
"$group": {
"_id": "$user",
"docs": {
"$push": "$$ROOT"
}
}
},
{
"$addFields": {
"filterStatus": {
"$reduce": {
"input": "$docs",
"initialValue": {
managerWithActiveTrue: false,
managerWithActiveFalse: false,
managerIsPresent: false,
userIsPresent: false
},
"in": {
"$mergeObjects": [
"$$value",
{
managerWithActiveTrue: {
"$or": [
{
"$and": [
{
"$eq": [
"$$this.role",
"manager"
]
},
"$$this.active"
]
},
"$$value.managerWithActiveTrue"
]
},
managerWithActiveFalse: {
"$or": [
{
"$and": [
{
"$eq": [
"$$this.role",
"manager"
]
},
{
$not: "$$this.active"
}
]
},
"$$value.managerWithActiveFalse"
]
},
managerIsPresent: {
"$or": [
{
"$eq": [
"$$this.role",
"manager"
]
},
"$$value.managerIsPresent"
]
},
userIsPresent: {
"$or": [
{
"$eq": [
"$$this.role",
"user"
]
},
"$$value.userIsPresent"
]
}
}
]
}
}
}
}
},
{
"$addFields": {
"docs": {
"$switch": {
"branches": [
{
"case": {
"$or": [
"$filterStatus.managerWithActiveTrue",
{
$not: "$filterStatus.userIsPresent"
}
]
},
"then": {
"$filter": {
"input": "$docs",
"as": "item",
"cond": {
"$eq": [
"$$item.role",
"manager"
]
}
}
}
},
{
"case": {
"$or": [
"$filterStatus.managerWithActiveFalse",
{
$not: "$filterStatus.managerIsPresent"
}
]
},
"then": {
"$filter": {
"input": "$docs",
"as": "item",
"cond": {
"$eq": [
"$$item.role",
"user"
]
}
}
}
}
],
default: "$docs"
}
}
}
},
{
"$unwind": "$docs"
},
{
"$replaceRoot": {
"newRoot": "$docs"
}
}
])
In this query, we first group the documents by username, then calculate a new field named filterStatus, in which store of the four given conditions which ones apply to the user. Finally, we filter the array using the filterStatus field in $switch, and then we unwind the array, and bring the docs to the root again, using $replaceRoot.
This is the playground link.
Related
I have two collections Posts an comments. I am storing comments with postID. I want to show comments count field when fetching all the posts data.
How do I achieve this?
// posts
{
postID: '123',
title: 'abc'
}
// comments
{
postID: '123',
comments: [
{
commentID: 'comment123',
comment: 'my Comment'
}
]
}
// Looking for this
{
postID: '123',
title: 'abc',
commentCount: 1
}
Here's one way you could do it.
db.posts.aggregate([
{
"$lookup": {
"from": "comments",
"localField": "postID",
"foreignField": "postID",
"pipeline": [
{
"$project": {
"_id": 0,
"commentCount": {"$size": "$comments"}
}
}
],
"as": "commentCount"
}
},
{
"$project": {
"_id": 0,
"postID": 1,
"title": 1,
"commentCount": {"$first": "$commentCount.commentCount"}
}
}
])
Try it on mongoplayground.net.
Try This.
pipeline = [{
"$lookup": {
"from": "comments",
"let": {
"postId": "$postId",
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": ["$postId", "$$postId"]
},
}
},
{
"$group": {
"_id": "$postId",
"comments_count": {"$sum": 1}
}
}
],
"as": "comments"
}
},
{
"$project": {
"_id": 0,
"postId": 1,
"title":1,
"comments_count": "$comments.comments_count"
}
}]
db.posts.aggregate(pipeline)
i have the following aggregate function in my code to count how many times a value is found in the db:
let data: any = await this.dataModel.aggregate(
[
{
$match: {
field: new ObjectID(fieldID),
},
},
{
$group: {
_id: "$value",
total_for_value: { $sum: 1 },
},
},
]
);
This works correctly, however my data setup is a bit different. I have two types of value fields. Some like this:
{
"_id" : ObjectId("123"),
"value" : "MALE"
}
and some like this:
{
"_id" : ObjectId("456"),
"value" : {
"value" : "MALE",
}
}
Is there a way to group the ones where the _id and the _id.value are the same? At the moment it counts them separately.
db.collection.aggregate([
{
"$addFields": {
"key2": {
"$cond": {
"if": {
$and: [
{
"$eq": [
{
"$type": "$key"
},
"object"
]
}
]
},
"then": "$key.value",
"else": "$key"
}
}
}
},
{
"$group": {
"_id": "$key2",
"data": {
$push: "$$ROOT"
}
}
}
])
This would do the job if _id.value is an object.
Playground
How do I group the date field one to the next one in each document?
{
_id: ObjectId('...'),
date: [ISODate('2022-05-27T00:00:00.000+00:00'), ISODate('2022-05-28T00:00:00.000+00:00') ...]
}
The desired document would like this:
{
_id: ObjectId('...'),
date1: ISODate('2022-05-27T00:00:00.000+00:00'),
date2: ISODate('2022-05-28T00:00:00.000+00:00')
}
Here's one way to do it.
db.collection.aggregate([
{ "$replaceWith": {
"$mergeObjects": [
{ "_id": "$_id" },
{ "$arrayToObject": {
"$map": {
"input": { "$range": [ 0, { "$size": "$date" } ] },
"as": "dateNum",
"in": {
"k": {
"$concat": [
"date",
{ "$toString": { "$add": [ "$$dateNum", 1 ] } }
]
},
"v": { "$arrayElemAt": [ "$date", "$$dateNum" ] }
}
}
}
}
]
}
}
])
Try it on mongoplayground.net.
Collection of my database is something like below
[{
_id:1,
status:"active",
sale: 4,
createdAt:"2019-10-08 08:46:19"
},
{
_id:2,
status:"inactive",
sale:5,
createdAt:"2019-10-08 06:41:19"
},
{
_id:2,
status:"inactive",
sale:5,
createdAt:"2019-10-08 02:01:19"
}]
I need to group it by "day".The result which I want
[{
createdAt:"2019-10-08 02:01:19",
inactive: 2,
active:1,
salesOfActive: 4,
salesOfInactive:10
}]
I am not getting the actual result which I want any help will be highly appreciated
I had try with this but won't get an idea how i will get salesOfActive and salesOfInactive per day
{
$group: {
_id: {
day: { $dayOfMonth: "$createdAt" }
},
inActive:{$sum: { status:"inactive"}},
active:{$sum: { status:"active"}},
salesOfActive: { $sum:$sale }
}
}
Basically you need to $sum each field $conditionally here
db.collection.aggregate([
{ "$group": {
"_id": {
"$dayOfMonth": { "$dateFromString": { "dateString": "$createdAt" } }
},
"inactive": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, 1, 0] }
},
"active": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, 0, 1] }
},
"salesOfInactive": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, "$sale", 0] }
},
"salesOfActive": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, 0, "$sale"] }
}
}}
])
MongoPlayground
Assuming I have a schema that looks something like this:
{
field: [{
subDoc: ObjectId,
...
}],
...
}
and I have some list of ObjectIds (user input), how would I get a count of those specific ObjectIds? For exmaple, if I have data like this:
[
{field: [ {subDoc: 123}, {subDoc: 234} ]},
{field: [ {subDoc: 234}, {subDoc: 345} ]},
{field: [ {subDoc: 123}, {subDoc: 345}, {subDoc: 456} ]}
]
and the list of ids given by the user is 123, 234, 345, I need to get a count the given ids, so a result approximating this:
{
123: 2,
234: 2,
345: 2
}
What would be the best way to go about this?
The aggregation framework itself if not going to dynamically name keys the way you have presented as a proposed output, and that probably is a good thing really. But you can probably just do a query like this:
db.collection.aggregate([
// Match documents that contain the elements
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
// De-normalize the array field content
{ "$unwind": "$field" },
// Match just the elements you want
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
// Count by the element as a key
{ "$group": {
"_id": "$field.subDoc",
"count": { "$sum": 1 }
}}
])
That gives you output like this:
{ "_id" : 345, "count" : 2 }
{ "_id" : 234, "count" : 2 }
{ "_id" : 123, "count" : 2 }
But if you really want to go nuts on this, you are specifying the "keys" that you want as part of your query, so you could form a pipeline like this:
db.collection.aggregate([
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
{ "$unwind": "$field" },
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
{ "$group": {
"_id": "$field.subDoc",
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": null,
"123": {
"$max": {
"$cond": [
{ "$eq": [ "$_id", 123 ] },
"$count",
0
]
}
},
"234": {
"$max": {
"$cond": [
{ "$eq": [ "$_id", 234 ] },
"$count",
0
]
}
},
"345": {
"$max": {
"$cond": [
{ "$eq": [ "$_id", 345 ] },
"$count",
0
]
}
}
}}
])
Which is a relatively simple thing to construct that last stage in code by just processing the list of arguments:
var list = [123,234,345];
var group2 = { "$group": { "_id": null } };
list.forEach(function(id) {
group2["$group"][id] = {
"$max": {
"$cond": [
{ "$eq": [ "$_id", id ] },
"$count",
0
]
}
};
});
And that comes out more or less how you want it.
{
"_id" : null,
"123" : 2,
"234" : 2,
"345" : 2
}
Not exactly what you're asking for but it can give you an idea:
db.test.aggregate([
{
$unwind: '$field'
},
{
$group: {
_id: {
subDoc: '$field.subDoc'
},
count: {
$sum: 1
}
}
},
{
$project: {
subDoc: '$subDoc.subDoc',
count: '$count'
}
}
]);
Output:
{
"result": [
{
"_id": {
"subDoc": 456
},
"count": 1
},
{
"_id": {
"subDoc": 345
},
"count": 2
},
{
"_id": {
"subDoc": 234
},
"count": 2
},
{
"_id": {
"subDoc": 123
},
"count": 2
}
],
"ok": 1
}