Group documents in two by array field Mongo Aggregation - javascript

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.

Related

How to achieve following output in Mongo Aggregation?

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.

Mongodb: How to pass another stage inside $group?

Expected Output:
[
{
"_id": "healty",
"doc_count": 2,
"ingredients": {
"Leaves": {
"1.2g": 1,
"1.5g": 1
},
"Spinach": {
"12g": 1,
"18g": 1
}
}
},
{
"_id": "junk",
"doc_count": 3,
"ingredients": {
"cheese": {
"100g": 1,
"120g": 2
},
"meat": {
"50g": 1,
"60g": 1,
"70g": 1
}
}
}
]
Aggregation Below: Playground1
db.collection.aggregate([
{
$group: {
"_id": "$type", // grouping the document by "type" field
"ingredients": {
$push: "$$ROOT" //want to run Playground2 aggrgtion in each of it
},
"doc_count": {
$sum: 1 // total count
}
}
}
])
After that,
I've also figured out an Another Step to convert the ingredients array after it: Playground2
But, this aggregation is for All the documents. I want to make Playground2 aggregation work for the ingredients field only in each group Object.
It's similar to combining Playground1 & Playground2. How do I do this?
db.collection.aggregate([
{
"$set": {
"ingredients": {
"$objectToArray": "$ingredients"
}
}
},
{
"$unwind": "$ingredients"
},
{
"$group": {
"_id": {
type: "$type",
ingredient: "$ingredients"
},
"count": {
"$sum": 1
},
"doc_count": {
"$addToSet": "$item"
}
}
},
{
"$group": {
"_id": {
type: "$_id.type",
ingredient: "$_id.ingredient.k"
},
"docs": {
"$push": {
k: "$_id.ingredient.v",
v: "$count"
}
},
"doc_count": {
"$push": "$doc_count"
}
}
},
{
"$group": {
"_id": "$_id.type",
"ingredients": {
"$push": {
k: "$_id.ingredient",
v: {
"$arrayToObject": "$docs"
}
}
},
"doc_count": {
"$push": {
$reduce: {
input: "$doc_count",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
}
},
{
"$set": {
"ingredients": {
"$arrayToObject": "$ingredients"
},
doc_count: {
"$size": {
"$setUnion": {
$reduce: {
input: "$doc_count",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
}
}
}
])
mongoplayground

MongoDB group aggregation with condition in $sum

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

$filter upto 3 nested level in mongodb

I have a below collection
[
{
"Array1": [
{
"Array2": [
{
"name": "6666",
"Array3": [
{ "_id": 128938120, "nest": "samsung" },
{ "_id": 12803918239, "nest": "nokia" }
]
},
{
"name": "5555",
"Array3": [
{ "_id": 48102938109, "nest": "iphone" },
{ "_id": 501293890, "nest": "micromax" }
]
}
],
"name": "old apartment"
},
{
"Array2": [
{
"_id": 410923810,
"name": "3333",
"Array3": [
{ "_id": 48102938190, "nest": "airtel" },
{ "_id": 48102938190, "nest": "jio" }
]
},
{
"_id": 41092381029,
"name": "2222",
"Array3": [
{ "_id": 10293182309, "nest": "master" },
{ "_id": 38190238, "nest": "cub" }
]
}
],
"name": "new apartment"
}
]
}
]
I want to fo $filter to 3 nested level... I want only nokia element from 3rd array and 6666 element from second and old apartment from the first
I want this output
[
{
"Array1": [
{
"Array2": [
{
"name": "6666",
"Array3": [
{
"_id": 12803918239,
"nest": "nokia"
}
]
}
],
"name": "old apartment"
}
]
}
]
And also I want to do using $filter and $map only... Don't want to use $unwind here
Basically you have to use $filter for each level to apply your condition and $map for each array that has nested array inside. That's because you want to pass filtered array to the output. So there will be 3 filters and 2 maps. Indentations might be really helpful in this case. Try:
db.collection.aggregate([
{
$project: {
Array1: {
$map: {
input: { $filter: { input: "$Array1", as: "a1", cond: { $eq: ["$$a1.name", "old apartment" ] } } },
as: "a1",
in: {
name: "$$a1.name",
Array2: {
$map: {
input: { $filter: { input: "$$a1.Array2", as: "a2", cond: { $eq: [ "$$a2.name", "6666" ] } } },
as: "a2",
in: {
name: "$$a2.name",
Array3: { $filter: { input: "$$a2.Array3", as: "a3", cond: { $eq: [ "$$a3.nest", "nokia" ] } } }
}
}
}
}
}
}
}
}
])
Mongo playground

MongoDB (mongoose) aggregate count instances of specific ObjectIDs in collection

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
}

Categories