How to use nested group in mongodb? - node.js

I have one query where I want to group subdocument. i have try some example but it is not working properly
Query
db.getCollection("checklistCombination").aggregate([
{
"$lookup": {
"from": "Users",
"localField": "userId",
"foreignField": "_id",
"as": "user"
}
},
{
"$lookup": {
"from": "checklist",
"localField": "checklistId",
"foreignField": "_id",
"as": "linkChecklist"
}
},
{ "$unwind": "$linkChecklist" },
{
"$lookup": {
"from": "orderDetail",
"localField": "linkChecklist.product",
"foreignField": "productRangeId",
"as": "orderProduct"
}
},
{
"$unwind": { "path": "$orderProduct", "preserveNullAndEmptyArrays": true }
},
{
"$lookup": {
"from": "companysuppliers",
"localField": "orderProduct.supplierId",
"foreignField": "_id",
"as": "comapnySupplier"
}
},
{
"$unwind": {
"path": "$comapnySupplier",
"preserveNullAndEmptyArrays": true
}
},
{
"$lookup": {
"from": "suppliers",
"localField": "comapnySupplier.supplierId",
"foreignField": "_id",
"as": "supplier"
}
},
{ "$unwind": { "path": "$supplier", "preserveNullAndEmptyArrays": true } },
{
"$project": {
"_id": 1,
"name": 1,
"user": 1,
"linkChecklist": 1,
"orderProduct": 1,
"orderProductStatusIndex": {
"$ifNull": ["$orderProduct.statusIndex", "0"]
},
"comapnySupplier": 1,
"supplier": 1
}
},
{ "$match": { "orderProductStatusIndex": "0" } },
{
"$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"supplier": {
"$push": {
"supplierId": "$supplier._id",
"supplierName": "$supplier.name",
"items": { "$sum": "$orderProduct.quantity" }
}
}
}
}
])
This query return below result
[{
"_id" : ObjectId("5cee224b97e765079c8c2839"),
"name" : "Dairy",
"supplier" : [
{
"supplierId" : ObjectId("5cee12a7a01ad50f5c2229ac"),
"supplierName" : "Bhagwandas Bherumal",
"items" : 10
}
]
},
{
"_id" : ObjectId("5cee1a19a01ad50f5c2229f2"),
"name" : "dairy/fruit",
"supplier" : [
{
"supplierId" : ObjectId("5cee12a7a01ad50f5c2229ac"),
"supplierName" : "Bhagwandas Bherumal",
"items" : 55
},
{
"supplierId" : ObjectId("5cee11f7a01ad50f5c2229a2"),
"supplierName" : "Agron India PVT. LTD",
"items" : 55
},
{
"supplierId" : ObjectId("5cee12a7a01ad50f5c2229ac"),
"supplierName" : "Bhagwandas Bherumal",
"items" : 10
}
]
}]
In result you can see there are two different results for Bhagwandas Bherumal in dairy/fruit (in array index 1 ). I want to group by this field and sum its items.
Expected Result
[
{
"_id" : ObjectId("5cee224b97e765079c8c2839"),
"name" : "Dairy",
"supplier" : [
{
"supplierId" : ObjectId("5cee12a7a01ad50f5c2229ac"),
"supplierName" : "Bhagwandas Bherumal",
"items" : 10
}
]
},
{
"_id" : ObjectId("5cee1a19a01ad50f5c2229f2"),
"name" : "dairy/fruit",
"supplier" : [
{
"supplierId" : ObjectId("5cee12a7a01ad50f5c2229ac"),
"supplierName" : "Bhagwandas Bherumal",
"items" : 65
},
{
"supplierId" : ObjectId("5cee11f7a01ad50f5c2229a2"),
"supplierName" : "Agron India PVT. LTD",
"items" : 55
}
]
}]

Hope this solves your problem :
db.getCollection("checklistCombination").aggregate([
{
"$lookup": {
"from": "Users",
"localField": "userId",
"foreignField": "_id",
"as": "user"
}
},
{
"$lookup": {
"from": "checklist",
"localField": "checklistId",
"foreignField": "_id",
"as": "linkChecklist"
}
},
{ "$unwind": "$linkChecklist" },
{
"$lookup": {
"from": "orderDetail",
"localField": "linkChecklist.product",
"foreignField": "productRangeId",
"as": "orderProduct"
}
},
{
"$unwind": { "path": "$orderProduct", "preserveNullAndEmptyArrays": true }
},
{
"$lookup": {
"from": "companysuppliers",
"localField": "orderProduct.supplierId",
"foreignField": "_id",
"as": "comapnySupplier"
}
},
{
"$unwind": {
"path": "$comapnySupplier",
"preserveNullAndEmptyArrays": true
}
},
{
"$lookup": {
"from": "suppliers",
"localField": "comapnySupplier.supplierId",
"foreignField": "_id",
"as": "supplier"
}
},
{ "$unwind": { "path": "$supplier", "preserveNullAndEmptyArrays": true } },
{
"$project": {
"_id": 1,
"name": 1,
"user": 1,
"linkChecklist": 1,
"orderProduct": 1,
"orderProductStatusIndex": {
"$ifNull": ["$orderProduct.statusIndex", "0"]
},
"comapnySupplier": 1,
"supplier": 1
}
},
{ "$match": { "orderProductStatusIndex": "0" } },
{ $group : {
_id : {
_id : '$_id',
name : '$name',
supplierId : "$supplier._id",
supplierName : "$supplier.name"
},
items : { "$sum": "$orderProduct.quantity"}
}
},
{
$group : {
_id : '$_id._id',
name : { "$first": "$_id.name" },
supplier : {
"$push": {
"supplierId": "$_id.supplierId",
"supplierName": "$_id.supplierName",
"items" : '$items'
}
}
}
}
])

Related

MongoDB - How to return an aggregation with reference to a refence using NodeJS

I am trying to do a lookup from multiple references
Here is a Mongo Playground
Here is my data
Insp
The Insp document contains an array of references to Users (by user ID)
[
{
"_id": {
"$oid": "6359a12fb9450da3d8d8cdd2"
},
"REF_Users": [
{
"$oid": "6359a0f1b9450da3d8d8cdc7"
},
{
"$oid": "6359a070f1e84209e0c78fc2"
}
],
"name": "Once"
}
]
Users
The Users document contains information about a user and it has a reference to the UserType (by userType ID)
[
{
"_id": {
"$oid": "6359a070f1e84209e0c78fc2"
},
"REF_UserType": {
"$oid": "63596323b679475de490500a"
},
"fName": "Billy"
},
{
"_id": {
"$oid": "6359a0f1b9450da3d8d8cdc7"
},
"REF_UserType": {
"$oid": "63596323b679475de4905007"
},
"fName": "Mike"
}
]
UserType
The UserType document holds type information
[
{
"_id": {
"$oid": "63596323b679475de4905007"
},
"value": 100,
"name": "INS"
},
{
"_id": {
"$oid": "63596323b679475de490500a"
},
"value": 200,
"name": "CLS"
}
]
Expected output
I want the userType for each user to be with the respective user
{
"_id": "6359a12fb9450da3d8d8cdd2",
"people": [
{
"_id": "6359a070f1e84209e0c78fc2",
"userType": {
"_id": "63596323b679475de490500a",
"value": 200,
"name": "CLS"
},
"fName": "Billy"
},
{
"_id": "6359a0f1b9450da3d8d8cdc7",
"userType": {
"_id": "63596323b679475de4905007",
"value": 100,
"name": "INS"
},
"fName": "Mike"
}
]
}
TRY 1
This is my pipeline so far
[
{
"$match": {}
},
{
"$lookup": {
"from": "users",
"localField": "REF_Users",
"foreignField": "_id",
"as": "people"
}
},
{
"$lookup": {
"from": "usertypes",
"localField": "people.REF_UserType",
"foreignField": "_id",
"as": "userType"
}
},
{
"$project": {
"REF_Users": 0,
"people.REF_UserType": 0
}
}
]
Result of TRY 1
{
"_id": "6359a12fb9450da3d8d8cdd2",
"people": [
{
"_id": "6359a070f1e84209e0c78fc2",
"fName": "Billy"
},
{
"_id": "6359a0f1b9450da3d8d8cdc7",
"fName": "Mike"
}
],
"userType": [
{
"_id": "63596323b679475de4905007",
"value": 100,
"name": "INS"
},
{
"_id": "63596323b679475de490500a",
"value": 200,
"name": "CLS"
}
]
}
It works in Compass...
It works in the playground
When I put the code into NodeJS and run it from my server:
TRY 2
const agg_project_try = {
people: {
$map: {
input: '$people',
as: 'people',
in: {
$mergeObjects: [
'$$people',
{
userType: {
$first: {
$filter: {
input: '$userType',
cond: {
$eq: ['$$people.REF_UserType', '$$this._id'],
},
},
},
},
},
],
},
},
},
};
I get this error
Arguments must be aggregate pipeline operators
TRY 3
I exported from Compass as NODE
[
{
'$lookup': {
'from': 'users',
'localField': 'REF_Users',
'foreignField': '_id',
'as': 'people'
}
}, {
'$lookup': {
'from': 'usertypes',
'localField': 'people.REF_UserType',
'foreignField': '_id',
'as': 'userType'
}
}, {
'$project': {
'people': {
'$map': {
'input': '$people',
'as': 'people',
'in': {
'$mergeObjects': [
'$$people', {
'userType': {
'$first': {
'$filter': {
'input': '$userType',
'cond': {
'$eq': [
'$$people.REF_UserType', '$$this._id'
]
}
}
}
}
}
]
}
}
}
}
}, {
'$unset': 'people.REF_UserType'
}
]
Then tried the 'project' portion in my server
const agg_project_try = {
'people': {
'$map': {
'input': '$people',
'as': 'people',
'in': {
'$mergeObjects': [
'$$people', {
'userType': {
'$first': {
'$filter': {
'input': '$userType',
'cond': {
'$eq': [
'$$people.REF_UserType', '$$this._id'
]
}
}
}
}
}
]
}
}
}
};
I get this error
Arguments must be aggregate pipeline operators
Here is my node JS pipeline ( that causes the error )
[
{ "$match": {} },
{ "$lookup": { "from": "users", "localField": "REF_Users", "foreignField": "_id", "as": "people" } },
{ "$lookup": { "from": "usertypes", "localField": "people.REF_UserType", "foreignField": "_id", "as": "userType" } },
{
"people": {
"$map": {
"input": "$people",
"as": "people",
"in": {
"$mergeObjects": [
"$$people",
{
"userType": {
"$first": {
"$filter": { "input": "$userType", "cond": { "$eq": ["$$people.REF_UserType", "$$this._id"] } }
}
}
}
]
}
}
}
},
{ "$project": { "REF_Users": 0 } }
]
ANSWER
Up too late last night working on this stuff, actually need the "project" statement to do a projection - doh !
$project:{
'people': {
$map: {
input: '$peopleLookup',
as: 'tempPeople',
in: {
$mergeObjects: [
'$$tempPeople',
{
'userType': {
$first: {
$filter: {
input: '$userTypeLookup',
cond: {
$eq: ['$$tempPeople.REF_UserType', '$$this._id'],
},
},
},
},
},
],
},
},
},
}
Thank you!
In $project stage, you need to iterate each document from the people array`.
Merge ($merge) the current iterated document with the first ($first) filtered ($filter) result from the userType array.
db.workoutDetailSchema.aggregate([
{
"$match": {}
},
{
"$lookup": {
"from": "users",
"localField": "REF_Users",
"foreignField": "_id",
"as": "people"
}
},
{
"$lookup": {
"from": "usertypes",
"localField": "people.REF_UserType",
"foreignField": "_id",
"as": "userType"
}
},
{
"$project": {
"people": {
$map: {
input: "$people",
as: "people",
in: {
$mergeObjects: [
"$$people",
{
userType: {
$first: {
$filter: {
input: "$userType",
cond: {
$eq: [
"$$people.REF_UserType",
"$$this._id"
]
}
}
}
}
}
]
}
}
}
}
},
{
$unset: "people.REF_UserType"
}
])
Demo # Mongo Playground
I just merged the documents using javascript.
Demo#mongoplayground
db.workoutDetailSchema.aggregate([
{
"$match": {}
},
{
"$lookup": {
"from": "users",
"localField": "REF_Users",
"foreignField": "_id",
"as": "peoples"
}
},
{
"$lookup": {
"from": "usertypes",
"localField": "peoples.REF_UserType",
"foreignField": "_id",
"as": "userType"
}
},
{
$addFields: {
people: {
$function: {
body: "function (people, userType) {people.forEach(function (item, index) {if(JSON.stringify(userType[index]._id) === JSON.stringify(item.REF_UserType)){people[index].userType=userType[index];}});return people;}",
args: [
"$peoples",
"$userType"
],
lang: "js"
},
}
}
},
{
"$project": {
"REF_Users": 0,
"peoples": 0,
"userType": 0,
"people.REF_UserType": 0,
}
}
])
Output
[
{
"_id": ObjectId("6359a12fb9450da3d8d8cdd2"),
"name": "Once",
"people": [
{
"_id": ObjectId("6359a0f1b9450da3d8d8cdc7"),
"fName": "Mike",
"userType": {
"_id": ObjectId("63596323b679475de4905007"),
"name": "INS",
"value": 100
}
},
{
"_id": ObjectId("6359a070f1e84209e0c78fc2"),
"fName": "Billy",
"userType": {
"_id": ObjectId("63596323b679475de490500a"),
"name": "CLS",
"value": 200
}
}
]
}
]

How to perform multiple $lookup for array mongodb aggregate

How can i perform $lookup in aggregate (mongodb) for an array
{
messages: [{
"_id" : ObjectId("5bfc43f2bbc4176ecc1c5f83"),
"text" : "text1",
"sender" : {
"id" : "36046fc2e70dd508a0bf1f36fd2daa20"
}
}, {
"_id" : ObjectId("5bfc43f2bbc4176ecc1c5f83"),
"text" : "text2",
"sender" : {
"id" : "36046fc2e70dd508a0bf1f36fd2daa22"
}
}],
"filed1": { ... },
"filed2": { ... }
}
how can i do a $lookup for sender id from accounts collection?
tried:
...
{
$lookup: {
from: "accounts",
localField: "messages.sender.id",
foreignField: "id",
as: "messages.sender.user"
}
}
...
You can use below aggregation
db.collection.aggregate([
{ "$unwind": "$messages" },
{ "$lookup": {
"from": "accounts",
"localField": "messages.sender.id",
"foreignField": "id",
"as": "messages.sender.user"
}},
{ "$unwind": "$messages.sender.user" }
{ "$group": {
"_id": "$_id",
"messages": { "$push": "$messages" },
"filed1": { "$first": "$filed1" },
"filed2": { "$first": "$filed2" }
}}
])

Get only one document of each user - mongoDB

I m stuck with a mongo aggregate query. Right now I have a collection which contains posts of various users (whose details are present in users collection).
I need a query to fetch only one post of each user (like group by in SQL)
POSTS collection data
{
language:'english',
status:'A',
desc:'Hi there',
userId:'5b891370f43fe3302bbd8918'
},{
language:'english',
status:'A',
desc:'Hi there - 2'
userId:'5b891370f43fe3302bbd8918'
},{
language:'english',
status:'A',
desc:'Hi there - 3'
userId:'5b891370f43fe3302bbd8001'
}
Here is my query
db.col('posts').aggregate([
{
$match: {
language: 'english',
status: "A"
}
}, {
$sample: { size: 10 }
}, {
$sort: { _id: -1 }
}, {
$lookup: {
from: 'users',
localField: 'userId',
foreignField: '_id',
as: 'ownerData'
}
}], (err, data) => { console.log(err,data) });
Desired Output
{
language:'english',
status:'A',
desc:'Hi there',
userId:'5b891370f43fe3302bbd8918',
ownerData:[[object]]
},{
language:'english',
status:'A',
desc:'Hi there - 3'
userId:'5b891370f43fe3302bbd8001',
ownerData:[[object]]
}
$group: will as group by of mysql. $first: will take first element of collection field from group. $lookup acts as join in mysql.
db.tempdate.aggregate([
{ $group :
{
_id : "$userId",
language : { $first: '$language' },
status : { $first: '$status' },
desc : { $first: '$desc' }
}
},
{ $lookup:
{
from: "user",
localField: "_id",
foreignField: "user_id",
as: "userData"
}
}
]).pretty();`
Output
`{
"_id" : "5b891370f43fe3302bbd8001",
"language" : "english",
"status" : "A",
"desc" : "Hi there - 3",
"userData" : [
{
"_id" : ObjectId("5ba3633a12b8613823f3056e"),
"user_id" : "5b891370f43fe3302bbd8001",
"name" : "Bhuwan"
}
]
}
{
"_id" : "5b891370f43fe3302bbd8918",
"language" : "english",
"status" : "A",
"desc" : "Hi there",
"userData" : [
{
"_id" : ObjectId("5ba3634612b8613823f3056f"),
"user_id" : "5b891370f43fe3302bbd8918",
"name" : "Harry"
}
]
}
Also, you can use group and $last
db.getCollection('posts').aggregate([
{ "$match": { "language": 'english', "status": "A" }},
{ "$group": {
"_id": "$userId",
"primaryId" : { "$last": "$_id" },
"language": { "$last": "$language" },
"status": { "$last": "$status" },
"desc": { "$last": "$desc" }
}},
{ "$lookup": {
"from": "users",
"localField": "_id",
"foreignField": "_id",
"as": "ownerData"
}},
{ $unwind:{path: '$ownerData',preserveNullAndEmptyArrays: true} //to convert ownerData to json object
}
])
You can use $group aggregation stage for the distinct userId and then use $lookup to get users data.
db.col('posts').aggregate([
{ "$match": { "language": 'english', "status": "A" }},
{ "$sample": { "size": 10 }},
{ "$sort": { "_id": -1 }},
{ "$group": {
"_id": "$userId",
"language": { "$first": "$language" },
"status": { "$first": "$status" },
"desc": { "$first": "$desc" }
}},
{ "$lookup": {
"from": "users",
"localField": "_id",
"foreignField": "_id",
"as": "ownerData"
}}
])

Node.js export to csv using - csv-express

I'm using npm "csv-express" package to export data to CSV. I'm having trouble getting the nested vendor name to display in the exported file. I used the $lookup (aggregation) to get the vendor name from another collection.
Here what the exported file look like:
Here is my query:
db.getCollection('systems').aggregate([
{ "$sort": { "sponsor": 1 } },
{ "$lookup": {
"from": "vendors",
"localField": "vendorID",
"foreignField": "_id",
"as": "vendor"
}
},
{ "$project":{
"sponsor":1,
"address":1,
"city":1,
"state":1,
"vendor.name":1
}
}])
Here's what the query returns:
/* 1 */
{
"_id" : ObjectId("5ab55fee294f2366c054d5eb"),
"sponsor" : "John Doe",
"address" : "123 Western Ave",
"city" : "Los Angeles",
"state" : "CA",
"vendor" : [
{
"name" : "West Interactive"
}
]
}
I tried the following but had no luck.
db.getCollection('systems').aggregate([
{ "$sort": { "sponsor": 1 } },
{ "$lookup": {
"from": "vendors",
"localField": "vendorID",
"foreignField": "_id",
"as": "vendor"
}
},
{ "$project":{
"sponsor":1,
"address":1,
"city":1,
"state":1,
"vendor.[0].name":1
}
}])
Any suggestion would be greatly appreciated!
I found a great article on how to accomplish what I needed.
Here is update query:
db.getCollection('systems').aggregate([
{ "$lookup": {
"from": "vendors",
"localField": "vendorID",
"foreignField": "_id",
"as": "vendor"
}
},
**{ $unwind: "$vendor" },**
{ "$project":{
"sponsor":1,
"address":1,
"city":1,
"state":1,
**"vendorName":"$vendor.name"**
}
}])
This returned the data need for my export.
/* 1 */
{
"_id" : ObjectId("5ab55fee294f2366c054d5eb"),
"sponsor" : "John Doe",
"address" : "123 Western Ave",
"city" : "Los Angeles",
"state" : "CA",
"vendorName" : "Acme Inc."
}

Pagination on mongoDB aggregations [Node.js]

I have query something like this:
Message.aggregate([{
"$match": {
$or: [{
"to": userId
}, {
"from": userId
}]
}
},
{
"$sort": {
"createDate": -1
}
},
{
"$group": {
"_id": "$conversationId",
"from": {
"$first": "$from"
},
"to": {
"$first": "$to"
},
"content": {
"$first": "$content"
},
"createDate": {
"$first": "$createDate"
},
"unreaded": {
"$sum": {
"$cond": {
if: {
$and: [{
"$eq": [
"$unreaded", 1
]
},
{
"$eq": ["$to", userId]
}
]
},
then: 1,
else: 0
}
}
}
}
},
{
"$sort": {
"createDate": -1
}
},
{
"$lookup": {
"from": "users",
"localField": "from",
"foreignField": "_id",
"as": "from"
}
},
{
"$lookup": {
"from": "users",
"localField": "to",
"foreignField": "_id",
"as": "to"
}
},
{
"$unwind": {
"path": "$from"
}
},
{
"$unwind": {
"path": "$to"
}
},
{
"$project": {
"from.firstName": "$from.firstName",
"from.lastName": "$from.lastName",
"from.picture": "$from.picture",
"to.firstName": "$to.firstName",
"to.lastName": "$to.lastName",
"to.picture": "$to.picture",
"content": 1,
"createDate": 1,
"unreaded": 1,
"reciver": {
"$cond": {
if: {
"$eq": ["$from._id", mongoose.Types.ObjectId(userId)]
},
then: {
"firstName": "$to.firstName",
"lastName": "$to.lastName",
"_id": "$to._id"
},
else: {
"firstName": "$from.firstName",
"lastName": "$from.lastName",
"_id": "$from._id"
}
}
}
}
},
{
"$limit": 50
}
I am able now to limit records to 50 per request but problem is when I try to make pagination... I get this error when I try to add skip next to limit:
Error: Arguments must be aggregate pipeline operators
any idea how can I do that?
Check your args and correct mistake.
Message.aggregate([{"$match": {$or: [{"to": userId}, {"from": userId}]}}, ..., {$skip: 1}, {$limit: 1}])

Resources