How to group query result by a specific field in MongoDB without mutating the object? - mongodb

I have a sample collection containing users and their departments
db.user.insertMany([
{
"department":"IT",
"firstName":{
"en":"Frodo",
"ru":"Фродо"
},
"lastName":{
"en":"Baggins",
"ru":"Беггинс"
}
},
{
"department":"IT",
"firstName":{
"en":"Bilbo",
"ru":"Билбо"
},
"lastName":{
"en":"Baggins",
"ru":"Беггинс"
}
},
{
"department":"Marketing",
"firstName":{
"en":"Merry",
"ru":"Мери"
},
"lastName":{
"en":"Brandybuck",
"ru":"Брендибак"
}
},
{
"department":"Marketing",
"firstName":{
"en":"Pippin",
"ru":"Пипин"
},
"lastName":{
"en":"Took",
"ru":"Тук"
}
}
])
Now what I want to do, is to query them grouped by the department, and apply localization.
So the desired output is
{
"IT":[
{
"_id":"5e808532ed036001cb7fe366",
"firstName":"Frodo",
"lastName":"Baggins"
},
{
"_id":"5e808532ed036001cb7fe367",
"firstName":"Bilbo",
"lastName":"Baggins"
}
],
"Marketing":[
{
"_id":"5e808532ed036001cb7fe368",
"firstName":"Merry",
"lastName":"Brandybuck"
},
{
"_id":"5e808532ed036001cb7fe369",
"firstName":"Pippin",
"lastName":"Took"
}
]
}
I have the following query which applies localization
db.user.aggregate([
{
$project: {
_id: '$_id',
firstName: '$firstName.en',
lastName: '$lastName.en'
}
}
]).pretty()
I looked through the $group documentation, but it seems to require an aggregate function, which is not what I need. How should I group the data?

You have to use $group.
db.collection.aggregate([
{
$group: {
_id: "$department",
data: {
$push: {
_id: "$_id",
firstName: "$firstName",
lastName: "$lastName"
}
}
}
},
{
$group: {
_id: null,
data: {
$push: {
k: "$_id",
v: "$data"
}
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$data"
}
}
}
])
Playground

Related

Mongoose subquery

I have a collection that looks like below:
[
{
"orderNum": "100",
"createdTime": ISODate("2020-12-01T21:00:00.000Z"),
"amount": 100,
"memo": "100memo",
"list": [
1
]
},
{
"orderNum": "200",
"createdTime": ISODate("2020-12-01T21:01:00.000Z"),
"amount": 200,
"memo": "200memo",
"list": [
1,
2
]
},
{
"orderNum": "300",
"createdTime": ISODate("2020-12-01T21:02:00.000Z"),
"amount": 300,
"memo": "300memo"
},
{
"orderNum": "400",
"createdTime": ISODate("2020-12-01T21:03:00.000Z"),
"amount": 400,
"memo": "400memo"
},
]
and I'm trying to get the total amount of orders that were created before order# 300 (so order#100 and #200, total amount is 300).
Does anyone know how to get it via Mongoose?
You can use this one:
db.collection.aggregate([
{ $sort: { orderNum: 1 } }, // by default the order of documents in a collection is undetermined
{ $group: { _id: null, data: { $push: "$$ROOT" } } }, // put all documents into one document
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } }, // cut desired elementes from array
{ $unwind: "$data" }, // transform back to documents
{ $replaceRoot: { newRoot: "$data" } },
{ $group: { _id: null, total_amount: { $sum: "$amount" } } } // make summary
])
Actually it is not needed to $unwind and $group, so the shortcut would be this:
db.collection.aggregate([
{ $sort: { orderNum: 1 } },
{ $group: { _id: null, data: { $push: "$$ROOT" } } },
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } },
{ $project: { total_amount: { $sum: "$data.amount" } } }
])
But the answer from #turivishal is even better.
Update for additional field
{
$set: {
data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] },
memo: { $arrayElemAt: [ "$data.memo", { $indexOfArray: ["$data.orderNum", "300"] } ] }
}
}
or
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } },
{ $set: { memo: { $last: { "$data.memo" } } },
$match orderNum less than 300
$group by null and get totalAmount using $sum of amount
YourSchemaModel.aggregate([
{ $match: { orderNum: { $lt: "300" } } },
{
$group: {
_id: null,
totalAmount: { $sum: "$amount" }
}
}
])
Playground

$match in aggregate don't return data in mongodb

I have three tables below is the structure like below
I'm looking to get a result like below
"type1": [ -- type from Accounts collection
{
"_id": "5e97e9a224f62f93d5x3zz46", -- _id from Accounts collection
"locs": "sampleLocks 1", -- field from Accounts collection
"solutions": "sample solutions 1", -- field from Accounts collection
"Clause": "clause 1" -- field from AccountsDesc collection
},
{
"_id": "5e97e9a884f62f93d5x3zz46",
"locs": "sampleLocks2",
"solutions": "sample solutions2",
"Clause": "clause2"
}
],
"type2": [
// same data construction as of type1 above
]
_id, locks, solution to be coming from Accounts collection
Clause field to be coming from AccountsDesc collection
accounts_id is kind of a foreign key in AccountsDesc coming from Account
competitor_id is kind of a foreign key in AccountsDesc coming from Competitor
Below is what my query looks like
db.accountDesc.aggregate([
{
$match : {accounts_Id : "123456"}, active: true}
},
{
$lookup: {
from: 'accounts',
pipeline: [{ $match: { type: { $in: ["type1, type2, type3"] } } }],
as: 'accountsData'
}
},
{
$group: {
_id: "$accountsData.type",
data: {
$push: {_id: "$accountsData._id", clause: "$clause", locs: "$type.locs", solutions: "$type.solutions"}
}
}
},
{
$group: {
_id: null,
data: {
$push: {
k: {
$toString: '$_id'
},
v: '$data'
}
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: '$data'
}
}
}
])
Issues related with the query -
$match : {accountId : "123456"}, active: true} -- No data is returned if i use match on AccountsDesc collection
cant set localField, foriegnField if im using pipeline, then how the mapping will happen like a LEFT join.
clause: "$clause" don't get the value of this field in the response
As we discussed in chat, you want RIGHT OUTER JOIN for your aggregation.
Try the query below:
db.User_Promo_Map.aggregate([
{
$match: {
user_Id: ObjectId("5e8c1180d59de1704ce68112")
}
},
{
$lookup: {
from: "promo",
pipeline: [
{
$match: {
active: true,
platform: {
$in: [
"twitch",
"youtube",
"facebook"
]
}
}
}
],
as: "accountsData"
}
},
{
$unwind: "$accountsData"
},
{
$group: {
_id: "$accountsData.platform",
data2: {
$addToSet: {
amount: "$amount",
promo_Id: "$promo_Id"
}
},
data: {
$addToSet: {
_id: "$accountsData._id",
format: "$accountsData.format",
description: "$accountsData.description"
}
}
}
},
{
$addFields: {
data: {
$map: {
input: "$data",
as: "data",
in: {
"_id": "$$data._id",
"description": "$$data.description",
"format": "$$data.format",
amount: {
$reduce: {
input: "$data2",
initialValue: "$$REMOVE",
in: {
$cond: [
{
$eq: [
"$$this.promo_Id",
"$$data._id"
]
},
"$$this.amount",
"$$value"
]
}
}
}
}
}
}
}
},
{
$group: {
_id: null,
data: {
$push: {
k: {
$toString: "$_id"
},
v: "$data"
}
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$data"
}
}
}
])
MongoPlayground

Mongodb Group stage, and query last two document

In mongodb, if we want to take first or last document of the group stage, then the FIRST and LAST operator will helpful. I want to group the collection based on _id:"$department" and also take the LAST document and the LAST-1 document. Is there any way to achieve this.
Assume you have this collection:
[
{ department: "HR", employees: 20 },
{ department: "Finance", employees: 30 },
{ department: "Sales", employees: 5 },
{ department: "IT", employees: 50 }
]
Then you can run this aggregation:
db.collection.aggregate([
{ $sort: { department: 1 } },
{
$group: {
_id: null,
employees: { $sum: "$employees" },
all_departments: { $push: "$department" }
}
},
{
$set: {
last_departments: {
$concatArrays: [
[{ $arrayElemAt: ["$all_departments", -1] }],
[{ $arrayElemAt: ["$all_departments", -2] }]
]
}
}
}
])
Mongo Playground
Update
With $slice it is even shorter:
db.collection.aggregate([
{ $sort: { department: 1 } },
{
$group: {
_id: null,
employees: { $sum: "$employees" },
all_departments: { $push: "$department" }
}
},
{ $set: { last_departments: { $slice: ["$all_departments", -2] } } }
])

mognodb aggregation group by actor

I have the following film collection structure:
{
"_id" : ObjectId,
"title" : "movie-1",
"actors" : [
"actor-1",
"actor-2",
"actor-3",
],
"categories" : [
"category-1",
"category-2"
]
}
I want to display result of all actors with associate movies and categories as like as given below:
{
"actor": "actor-1",
"result": {
"category-1": [ "movie-1", "movie-2" ],
"category-2": [ "movie-1", "movie-4" ]
}
}
I have tried aggregation as like as given below:
db.film.aggregate([
{ $unwind: "$actors" },
{ $group: {
_id: "$actors",
data: { $push: { movie: "$title", categories: "$categories" } }
}
},
{
$project: {
_id: 0,
actor: "$_id",
result: {
$reduce: {
input: "$data",
initialValue: {},
in: {
$let: {
vars: { movie: "$$this.movie", categories: "$$this.categories" },
in: {
$arrayToObject: {
$map: {
input: "$$categories",
in: { k: "$$this", v: "$$movie" }
}
}
}
}
}
}
}
}
}
])
But I get all actors list with only one movie with category as like as given below:
{
"actor" : "actor-1",
"result" : {
"category-1" : "movie-1",
"category-2" : "movie-2",
"category-3" : "movie-3"
}
}
How can I solve this problem? Thanks in advance.
You may need to do another $unwind on the categories array after flattening the actors array then group all the flattened docs by the two fields i.e. actor and category fields to create the movie titles list.
Another group to shape the result field is required.
The following pipeline should give you the desired result:
db.film.aggregate([
{ "$unwind": "$actors" },
{ "$unwind": "$categories" },
{ "$group": {
"_id": { "actor": "$actors", "category": "$categories" },
"movies": { "$push": "$title" }
} },
{ "$group": {
"_id": "$_id.actor",
"result": {
"$push": {
"k": "$_id.category",
"v": "$movies"
}
}
} },
{ "$addFields": {
"result": { "$arrayToObject": "$result" }
} }
])
I've used a sledgehammer to crack a nut (c)
Some stages could be replaced by $reduce, done inside $project stage (criticism and suggestions will be welcome)
db.film.aggregate([
{
$unwind: "$actors"
},
{
$group: {
_id: "$actors",
data: {
$push: {
movie: "$title",
categories: "$categories"
}
}
}
},
{
$unwind: "$data"
},
{
$unwind: "$data.categories"
},
{
$group: {
_id: {
actors: "$_id",
categories: "$data.categories"
},
movies: {
$push: "$data.movie"
}
}
},
{
$project: {
_id: 0,
actor: "$_id.actors",
result: {
k: "$_id.categories",
v: "$movies"
}
}
},
{
$group: {
_id: "$actor",
result: {
$push: "$result"
}
}
},
{
$project: {
_id: 0,
actor: "$_id",
result: {
$arrayToObject: "$result"
}
}
},
{
$sort: {
actor: 1
}
}
])
MongoPlayground

MongoDB get values with most recent date

I have a collection with documents such as these.
What I want to do is get all distinct clusters coming from the document with the highest (recent) lastupdate field.
I think this should be the output:
[
"19":"Income2",
"20":"Income Modified",
"21":"Income Modified"
]
Please try this :
db.yourCollection.aggregate([{ $unwind: '$meta.clusters' },
{ $project: { '_id': { $objectToArray: '$meta.clusters' }, 'last_update': 1 } }, { $sort: { 'last_update': -1 } },
{ $group: { _id: '$_id.k', values: { $first: '$$ROOT' } } }, { $sort: { 'values.last_update': -1 } },
{ $replaceRoot: { 'newRoot': '$values' } },
{ $group: { _id: '', distinctCLusters: { $push: { $arrayToObject: "$_id" } } } }, { $project: { _id: 0 } }])
Output with provided data:
{
"distinctCLusters" : [
{
"21" : "Income Modified"
},
{
"19" : "Income2"
},
{
"20" : "Income Modified"
}
]
}