Related
i'm trying to display a mongodb aggregation result via react chartjs. part of my problem is i'm not achieving the correct aggregation output in the backend.
current aggregation output which contains unwanted empty curly braces
[{
"_id":"Fubar",
"A_set":[{"A":"Y"},{"A":"N"},{},{}],
"A_count_set":[{"A_count":1},{"A_count":1},{},{}],
"B_set":[{},{},{"B":"N"},{"B":"Y"}],
"B_count_set":[{},{},{"B_count":1},{"B_count":1}]},
{
"_id":"Fubar2",
"A_set":[{"A":"Y"},{"A":"N"},{},{}],
"A_count_set":[{"A_count":1},{"A_count":1},{},{}],
"B_set":[{},{},{"B":"N"},{"B":"Y"}],
"B_count_set":[{},{},{"B_count":1},{"B_count":1}]
}]
i'm trying to achieve this target aggregation output which lacks the empty curly braces
[{
"_id":"Fubar",
"A_set":[{"A":"Y"},{"A":"N"}],
"A_count_set":[{"A_count":1},{"A_count":1}],
"B_set":[{"B":"N"},{"B":"Y"}],
"B_count_set":[{"B_count":1},{"B_count":1}]},
{
"_id":"Fubar2",
"A_set":[{"A":"Y"},{"A":"N"}],
"A_count_set":[{"A_count":1},{"A_count":1}],
"B_set":[{"B":"N"},{"B":"Y"}],
"B_count_set":[{"B_count":1},{"B_count":1}]
}]
pipeline manipulation
{$facet: {
A_branch: [
{$group: {
_id: {
Q_id: "$A_id",
A_id: "$A_id"
},
A_count: {$sum: 1}
}}
],
B_branch: [
{$group: {
_id: {
Q_id: "$Q_id",
B_id: "$B_id"
},
B_count: {$sum: 1}
}}
]
}},
{$project: {
combined_group: {$setUnion: ['$A_branch','$B_branch']}
}},
{$unwind: '$combined_group'},
{$lookup:
{
from: "Q",
localField: "combined_group._id.Q_id",
foreignField: "_id",
as: "QRef"
}
},
{$unwind: "$QRef" },
{$lookup:
{
from: "A",
localField: "combined_group._id.A_id",
foreignField: "_id",
as: "ARef"
}
},
{$unwind: {path:"$ARef", preserveNullAndEmptyArrays: true} },
{$lookup:
{
from: "B",
localField: "combined_group._id.B_id",
foreignField: "_id",
as: "BRef"
}
},
{$unwind: {path:"$BRef", preserveNullAndEmptyArrays: true} },
{$group: {
_id: "$QRef.text",
A_set: {
$push: {
A: "$ARef.value"
}
},
A_count_set: {
$push: {
A_count: "$combined_group.A_count"
}
},
B_set: {
$push: {
B: "$BRef.value"
}
},
B_count_set: {
$push: {
B_count: "$combined_group.B_count"
}
}
}}
aggregation input
{
"_id" : ObjectId("618..."),
"Q_id" : ObjectId("618..."),
"B_id" : ObjectId("618..."),
"A_id" : ObjectId("618...")
}
{
"_id" : ObjectId("618..."),
"Q_id" : ObjectId("618..."),
"B_id" : ObjectId("618..."),
"A_id" : ObjectId("618...")
}
{
"_id" : ObjectId("618..."),
"Q_id" : ObjectId("618..."),
"B_id" : ObjectId("618..."),
"A_id" : ObjectId("618...")
}
{
"_id" : ObjectId("618..."),
"Q_id" : ObjectId("618..."),
"B_id" : ObjectId("618..."),
"A_id" : ObjectId("618...")
}
use $filter in the end of pipeline
db.collection.aggregate([
{
"$set": {
"A_set": {
"$filter": {
"input": "$A_set",
"as": "x",
"cond": { "$ne": [ "$$x", {}] }
}
}
}
},
{
"$set": {
"A_count_set": {
"$filter": {
"input": "$A_count_set",
"as": "x",
"cond": { "$ne": [ "$$x", {}] }
}
}
}
},
{
"$set": {
"B_set": {
"$filter": {
"input": "$B_set",
"as": "x",
"cond": { "$ne": [ "$$x", {}] }
}
}
}
},
{
"$set": {
"B_count_set": {
"$filter": {
"input": "$B_count_set",
"as": "x",
"cond": { "$ne": [ "$$x", {}] }
}
}
}
}
])
mongoplayground
My Aggregate query:
const categoryId = req.query.categoryId
const results = await Question.aggregate([
{
$match:{
$and : [
{ category : mongoose.Types.ObjectId(categoryId) },
{ category : {$ne : null} }
]
}
},{
$lookup: {
from: "answer",
let: { questionId: "$_id" },
pipeline: [{ $match: { $expr: { $eq: ["$$questionId", "$questionId"] } } }],
as: "answerCount"
}
},{ $addFields: { answerCount: { $size: "$answerCount" }}}, {
$lookup: {
from: "users",
let : {id : "$creator"},
as : "creator",
pipeline : [
{$match : {$expr : {$eq: ["$$id","$_id"]}}},
{$project : {name : 1, profilePhoto : 1}}
]
}
}, {$unwind: "$creator"},{
$lookup: {
from: "categories",
let : { id: "$category" },
as : "category",
pipeline: [
{ $match : { $expr: { $eq: ["$_id", "$$id"] } }},
{ $project: { name: 1}}
]
}
}, {$unwind : "$category"},{
$unset: ["createdAt", "updatedAt", "__v"]
}
])
Now using $match query I fetch only the Questions belonging to a specific category. What I want to do is if the categoryId is null, it should return all the results. Right now it returns an empty array. How do i go about doing that?
Try This:
const categoryId = req.query.categoryId
let conditions = {
// You can also have some default condition that always results true
};
if (categoryId) {
conditions = {
"category": mongoose.Types.ObjectId(categoryId)
// More conditions in future...
}
}
const results = await Question.aggregate([
{
$match: conditions
},
{
$lookup: {
from: "answer",
let: { questionId: "$_id" },
pipeline: [{ $match: { $expr: { $eq: ["$$questionId", "$questionId"] } } }],
as: "answerCount"
}
},
{ $addFields: { answerCount: { $size: "$answerCount" } } },
{
$lookup: {
from: "users",
let: { id: "$creator" },
as: "creator",
pipeline: [
{ $match: { $expr: { $eq: ["$$id", "$_id"] } } },
{ $project: { name: 1, profilePhoto: 1 } }
]
}
},
{ $unwind: "$creator" },
{
$lookup: {
from: "categories",
let: { id: "$category" },
as: "category",
pipeline: [
{ $match: { $expr: { $eq: ["$_id", "$$id"] } } },
{ $project: { name: 1 } }
]
}
},
{ $unwind: "$category" },
{
$unset: ["createdAt", "updatedAt", "__v"]
}
]);
also read about preserveNullAndEmptyArrays property in $unwind operator if required.
https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/
i want to save records in a new collection using either $out or $merge.
**/////collection 2- reservationdeatils////**
"_id":ObjectId("5e4a898947363e964a886420"),
"phoneNo" : 98765#####,
"name" : "name1",
"userId":ObjectId("5e1efac668c3c811c83263cc"),
"approversId":ObjectId("5e1efad268c3c811c83263cd")
"bookedForDate":ISODate("2020-02-20T07:23:36.130Z"),
"bookingDetails" : [
{ "_id" : ObjectId("5e44f471d1868d2a54aac12d"),
"seatsBooked" : 15,
"floorId" : "#IKE01",
},
{ "_id" : ObjectId("5e44f471d1868d2a54aac12c"),
"seatsBooked" : 35,
"floorId" : "#HKE04",
}
],
**/////collection 2-priceDetails////**
{
"_id" : ObjectId("5e1efb0168c3c811c83263ce"),
"floorId" : "#IKE01",
"weekday" : "monday",
"pricePoint" : 589,
}
{
"_id" : ObjectId("5e2694db54e532a4eb92b477"),
"floorId" : "#IKE02",
"weekday" : "thursday",
"pricePoint" : 699
}
{
"_id" : ObjectId("5e2694f954e532a4eb92b478"),
"floorId" : "#HKE04",
"weekday" : "monday",
"pricePoint" : 579
}
**/////collection 3- discount////**
{
"_id" : ObjectId("5e427de64617181a4ce38893"),
"userId" : ObjectId("5e3d05ba964d0e06c4bb0f07"),
"approversId" : ObjectId("5e1d82156a67173cb877f67d"),
"floorId" : "#IKE01",
"weekday" : "monday",
"discount" : 20%,
},
{
"_id" : ObjectId("5e4281e7fec2e01a4c60b406"),
"userId" : ObjectId("5e1efac668c3c811c83263cc"),
"approversId" : ObjectId("5e1efad268c3c811c83263cd"),
"floorId" : "#IKE01",
"weekday" : "monday",
"discount" : 24%,
}
Now below is the query i have tried :
db.reservationdeatils.aggregate([
{
'$match': {
'approverId': ObjectId('5e1efad268c3c811c83263cd'),
'userId': ObjectId('5e1efac668c3c811c83263cc'),
'bookedForDate': ISODate("2020-02-11T18:30:00Z"),
}
},
{
'$unwind': {
'path': '$bookingDetails',
},
},
{
$lookup:
{
from: 'priceDetails',
let: { floorId: '$bookingDetails.floorId' },
pipeline: [
{
$match: {
weekday: 'monday',
$expr: {
$eq: ["$floorId", "$$floorId"]
}
}
}
], as: 'priceDetails'
}
},
{ '$unwind': '$priceDetails' },
{
$lookup:
{
from: 'discount',
let: { floorId: '$bookingDetails.floorId' },
pipeline: [
{
$match: {
weekday: 'monday',
$expr: {
$eq: ["$floorId", "$$floorId"]
}
}
}
], as: 'discounts'
}
},
{ '$unwind': '$discounts' },
{
'$group': {
'_id': {
'floorId': '$bookingDetails.floorId',
'date': '$bookedForDate',
'price': '$priceDetails.pricePoint',
'discount': '$discounts.discount'
},
'seatsBooked': {
'$sum': '$bookingDetails.seatsBooked'
},
}
},
{
'$project': {
'amount': {
'$multiply':
[
'$seatsBooked',
{'$subtract':
['$_id.pricePoint',
{ '$multiply':
['$_id.pricePoint',
{ '$divide':
['$_id.discount', 100]
}]
}]
}]
},
},
},
{
$group: {
_id: null,
totalAmount: {
$sum: "$amount"
}
}
},
{
'$project': {
_id:0,
totalAmount:1,
bookedForDate:1,
'floorId':'$priceDetails.floorId'
}
},{'$merge':'invoice'}
]).pretty()
i have been able to achieve the totalAmount but what i want to achieve is that i want to save these fields into "invoice" collection "userId","approversId","floorId","sum","totalSum","bookedForDate","name" BUT 1:whenever i use $out instead of $merge the previous document gets replaces which i dont want, 2: if i use $merge everytime i run the query a new document is created and that too only with _id:ObjectId(5e4a899c47363e964a88642f),totalBill:#### these fields , any suggestion how can i achieve this
You are going in a good direction you just need to look at the $group aggregation.
One more thing I have used the discount as int value, not in percentage.
"discount" : 24
I have updated the query:
db.reservationdeatils.aggregate([
{
$match: {
"userId" : ObjectId("5e1efac668c3c811c83263cc"),
"approversId" : ObjectId("5e1efad268c3c811c83263cd"),
"bookedForDate" : ISODate("2020-02-20T07:23:36.130Z")
}
},
{
$unwind: {
path: "$bookingDetails",
},
},
{
$lookup:
{
from: "priceDetails",
let: { floorId: "$bookingDetails.floorId" },
pipeline: [
{
$match: {
weekday: "monday",
$expr: {
$eq: ["$floorId", "$$floorId"]
}
}
}
],
as: "priceDetails"
}
},
{
$unwind: "$priceDetails"
},
{
$lookup:
{
from: "discount",
let: { floorId: "$bookingDetails.floorId" },
pipeline: [
{
$match: {
weekday: "monday",
$expr: {
$eq: ["$floorId", "$$floorId"]
}
}
}
],
as: "discounts"
}
}
,
{
$unwind: "$discounts"
}
,
{
$group: {
"_id": {
"floorId": "$bookingDetails.floorId",
"date": "$bookedForDate",
"price": "$priceDetails.pricePoint",
"discount": "$discounts.discount"
},
"price":{
$first:"$priceDetails.pricePoint"
},
"discount":{
$first:"$discounts.discount"
},
"seatsBooked": {
$sum: "$bookingDetails.seatsBooked"
},
}
}
,
{
$project: {
"amount": {
$multiply:
[
"$seatsBooked",
{
$subtract:[
"$price",
{
$multiply:[
"$price",
{
$divide:[
"$discount",
100
]
}
]
}
]
}
]
},
},
}
,
{
$group: {
"_id": null,
"totalAmount": {
$sum: "$amount"
}
}
},
{
$project: {
"_id":0,
"totalAmount":1,
"bookedForDate":1,
"floorId":"$priceDetails.floorId"
}
},
{
$out:"invoice"
}
]).pretty()
This will help you.
Hi below is the description of the issue i am facing
mongoShell query
db.masters.aggregate([
{
$match: {
_id: ObjectId("5e2554ec3405363bc4bf86c0")
}
}, {
$lookup: {
from: 'masters',
localField: 'mappedVendors',
foreignField: '_id',
as: 'mappedVendors'
}
}, { $unwind: '$mappedVendors'}, { $replaceRoot: { newRoot: "$mappedVendors" } },
{
$lookup:
{
from: "orders",
let: { mappedVendorId: "$_id" },
pipeline: [
{
$match: { $expr: { $eq: ["$orderCreatedBy", "$$mappedVendorId"] } }
},
{ $project: { orderCreatedOn: 1, isApproved: 1 } }
],
as: "orders"
}
},{
$lookup:
{
from: "payments",
let: { mappedVendorId: "$_id" },
pipeline: [
{
$match: { $expr: { $eq: ["$paymentDoneBy", "$$mappedVendorId"] } }
},
{ $project: { outstanding: 1 } }
],
as: "payments"
}
},
{ $project: { name: 1, phoneNo: 1, address: 1, depotCode: 1, orders: 1, payments: 1 } }
]).pretty()
response i am getting in mongoshell
{
"_id" : ObjectId("5e2555643405363bc4bf86c4"),
"phoneNo" : 9992625541,
"name" : "vendor4",
"address" : "4 vendor address 4",
"depotCode" : "D3139",
"orders" : [ ],
"payments" : [
{
"_id" : ObjectId("5dd7aa6c31eb913a4c4a487c"),
"outstanding" : 300
}
]
}
{
"_id" : ObjectId("5e2555783405363bc4bf86c5"),
"phoneNo" : 9992625542,
"name" : "vendor5",
"address" : "5 vendor address 5",
"depotCode" : "D3139",
"orders" : [
{
"_id" : ObjectId("5e2564323405363bc4bf86c6"),
"isApproved" : false,
"orderCreatedOn" : ISODate("2020-01-20T08:26:26.812Z")
},
{
"_id" : ObjectId("5e27fd3da42d441fe8a89580"),
"isApproved" : false,
"orderCreatedOn" : ISODate("2020-01-15T18:30:00Z")
}
],
This query in shell is working as expected in shell but when i am trying this in nodejs its returning empty[].
below is the description of my nodejs file
1: Mongodb Connection string
const mongoose = require('mongoose')
mongoose.connect('mongodb://127.0.0.1:27017/#####App', {
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify:false,
useUnifiedTopology: true
})
NOTE: ##### is not my code
2:nodejs controller
exports.vendorWiseIndent = async (req, res) => {
const { dealerId } = req.body
try {
const order = await Master.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId(dealerId)
}
}, {
$lookup: {
from: "masters",
localField: "mappedVendors",
foreignField: "_id",
as: "mappedVendors"
},
},
{ $unwind: "$mappedVendors" }, { $replaceRoot: { newRoot: "$mappedVendors" } },
{
$lookup:
{
from: "orders",
let: { mappedVendorId: "$_id" },
pipeline: [
{
$match: { $expr: { $eq: ["$orderCreatedBy", "$$mappedVendorId"] } }
},
{ $project: { orderCreatedOn: 1, isApproved: 1 } }
],
as: "orders"
}
}, {
$lookup:
{
from: "payments",
let: { mappedVendorId: "$_id" },
pipeline: [
{
$match: { $expr: { $eq: ["$paymentDoneBy", "$$mappedVendorId"] } }
},
{ $project: { outstanding: 1 } }
],
as: "payments"
}
},
{ $project: { name: 1, phoneNo: 1, address: 1, depotCode: 1, orders: 1, payments: 1 } }
])
console.log(order)
return res.status(200).json({
order
});
} catch (error) {
res.send(error);
}}
I have also tried it with just {_id: dealerId}
3"nodejs router file
router.post("/vendorwiseindent", vendorWiseIndent.vendorWiseIndent);
POSTMAN BODY & url
POST: http://localhost:5002/vendorwiseindent
{
"dealerId": "5e2554ec3405363bc4bf86c0"
}
POSTMAN RESPONSE:
{
"order": []
}
I have also tried it with just{ _id: dealerId}
now mongodb database contains multiple collections and i have already other API's running so the db which is connected is right,there has to be some other issue that this query is not working in nodejs or rather its returning an empty array as order:[] but the query is working in shell
"mongoose": "5.7.4" & mongodb version is 4.2
nodejs controller files need be checked,
ADD
`const mongoose = require('mongoose ')`
at the top
dealerId was not getting converted to objectID as it was missing, after its addition POSTMAN response is mentioned below:
{
"order": [
{
"_id": "5e2555643405363bc4bf86c4",
"phoneNo": 9992625541,
"name": "vendor4",
"address": "4 vendor address 4",
"depotCode": "D3139",
"orders": [],
"payments": [
{
"_id": "5dd7aa6c31eb913a4c4a487c",
"outstanding": 300
}
]
},
{
"_id": "5e2555783405363bc4bf86c5",
"phoneNo": 9992625542,
"name": "vendor5",
"address": "5 vendor address 5",
"depotCode": "D3139",
"orders": [
{
"_id": "5e2564323405363bc4bf86c6",
"isApproved": false,
"orderCreatedOn": "2020-01-20T08:26:26.812Z"
},
{
"_id": "5e27fd3da42d441fe8a89580",
"isApproved": false,
"orderCreatedOn": "2020-01-15T18:30:00.000Z"
}
],
"payments": []
}
]
}
Now consider the case , i have one document containing below collection like structure.
Below is the order collection
{
"_id" : ObjectId("5788fcd1d8159c2366dd5d93"),
"color" : "Blue",
"code" : "1",
"category_id" : ObjectId("5693d170a2191f9020b8c815"),
"description" : "julia tried",
"name" : "Order1",
"brand_id" : ObjectId("5b0e52f058b8287a446f9f05")
}
There is also a collection for Brand and Category. This is the
Category collection
{
"_id" : ObjectId("5693d170a2191f9020b8c815"),
"name" : "Category1",
"created_at" : ISODate("2016-01-11T20:32:17.832+0000"),
"updated_at" : ISODate("2016-01-11T20:32:17.832+0000"),
}
Brand Collection
{
"_id" : ObjectId("5b0e52f058b8287a446f9f05"),
"name" : "brand1",
"description" : "brand1",
"updated_at" : ISODate("2017-07-05T09:18:13.951+0000"),
"created_at" : ISODate("2017-07-05T09:18:13.951+0000"),
}
Now after aggregation applied, it should result in below format:
{
'brands': [
{
_id: '*******'
name: 'brand1',
categories: [
{
_id: '*****',
name: 'category_name1',
orders: [
{
_id: '*****',
title: 'order1'
}
]
}
]
}
]
}
You can try below aggregation:
db.brand.aggregate([
{
$lookup: {
from: "order",
localField: "_id",
foreignField: "brand_id",
as: "orders"
}
},
{
$unwind: "$orders"
},
{
$lookup: {
from: "category",
localField: "orders.category_id",
foreignField: "_id",
as: "categories"
}
},
{
$unwind: "$categories"
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
description: { $first: "$description" },
updated_at: { $first: "$updated_at" },
created_at: { $first: "$created_at" },
categories: { $addToSet: "$categories" },
orders: { $addToSet: "$orders" }
}
},
{
$addFields: {
categories: {
$map: {
input: "$categories",
as: "category",
in: {
$mergeObjects: [
"$$category", {
orders: [ {
$filter: {
input: "$orders",
as: "order",
cond: { $eq: [ "$$category._id", "$$order.category_id" ] }
}
} ]
} ]
}
}
}
}
},
{
$project: {
orders: 0
}
}
])
Basically you have to use $lookup twice to "merge" data from all these collections based on brand_id and category_id fields. Since you expect orders in categories in brands you can use $unwind for both arrays and then use $group to get following shape:
{
"_id" : ObjectId("5b0e52f058b8287a446f9f05"),
"name" : "brand1",
"description" : "brand1",
"updated_at" : ISODate("2017-07-05T09:18:13.951Z"),
"created_at" : ISODate("2017-07-05T09:18:13.951Z"),
"categories" : [
{
"_id" : ObjectId("5693d170a2191f9020b8c814"),
"name" : "Category1",
"created_at" : ISODate("2016-01-11T20:32:17.832Z"),
"updated_at" : ISODate("2016-01-11T20:32:17.832Z")
}
],
"orders" : [
{
"_id" : ObjectId("5788fcd1d8159c2366dd5d93"),
"color" : "Blue",
"code" : "1",
"category_id" : ObjectId("5693d170a2191f9020b8c814"),
"description" : "julia tried",
"name" : "Order1",
"brand_id" : ObjectId("5b0e52f058b8287a446f9f05")
}
]
}
Now you have brand1 with all its subcategories and all orders that should be placed in one of those categories. The only thing is how to "nest" orders in categories. One way to do that might be $map where you can merge each category with all orders that match that category (using $mergeObjects you don't have to specify all properties from categories object).
To match category with orders you can perform $filter on orders array.
Then you can drop orders since those are nested into categories so you don't need them anymore.
EDIT: 3.4 version
In MongoDB 3.4 you can't use $mergeObjects so you should specify all properties for `categories:
db.brand.aggregate([
{
$lookup: {
from: "order",
localField: "_id",
foreignField: "brand_id",
as: "orders"
}
},
{
$unwind: "$orders"
},
{
$lookup: {
from: "category",
localField: "orders.category_id",
foreignField: "_id",
as: "categories"
}
},
{
$unwind: "$categories"
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
description: { $first: "$description" },
updated_at: { $first: "$updated_at" },
created_at: { $first: "$created_at" },
categories: { $addToSet: "$categories" },
orders: { $addToSet: "$orders" }
}
},
{
$addFields: {
categories: {
$map: {
input: "$categories",
as: "category",
in: {
_id: "$$category._id",
name: "$$category.name",
created_at: "$$category.created_at",
updated_at: "$$category.updated_at",
orders: [
{
$filter: {
input: "$orders",
as: "order",
cond: { $eq: [ "$$category._id", "$$order.category_id" ] }
}
}
]
}
}
}
}
},
{
$project: {
orders: 0
}
}
])