Combine multiple projections with conditions in MongoDB aggregation - node.js

Orders.aggregate([{$match: { shippingType: "standardShipping" }},
{ $project: { standardShippingCount: { $size: "$products" } } },
])
Orders.aggregate([{$match: { shippingType: "expressShipping" } },
{ $project: { expressShippingCount: { $size: "$products" } } },
])
I need help to find out if it's possible to write these 2 queries in 1. Any help is appericated.

If you only need the number of total products ordered with each shippingType from the whole collection, then you can use this aggregate query
[
{
$unwind: {
path: "$products"
},
},
{
$group: {
_id: "$shippingType",
count: {
$count: {}
}
}
},
]
It will give you a response in this format
[
{
"_id": "expressShipping",
"count": 14
},
{
"_id": "standardShipping",
"count": 6
}
]
Now to convert it into a format that suits your use-case
queryResponse.reduce((acc, curr) => {
acc[`${curr._id}Count`] = curr.count;
return acc;
}, {})
Finally, you'll have this
{
expressShippingCount: 14,
standardShippingCount: 6
}

Related

select single document with minimum value $Min mongodb

I have made several efforts to select a single specific document that contains the minimum value from the database.
let Lowestdate = await BTCMongo.aggregate(
[
// { "$sort": { "name": 1,
{
$match : { "createdAt" : { $gte: new Date(last),$lte: new Date(NEW) } } },
{
$group:
{
_id:null,
minFee_doc:{$min: "$$ROOT"},
minFee: { $min:{$toDouble:"$one"}},
firstFee: { $first: "$one" },
lastFee: { $last: "$one" },
maxFee: { $max: {$toDouble:"$one"}},
}
},
]
).then(result => {}):
with minFee_doc:{$min: "$$ROOT"}, I have been trying to return the document containing the minimum $min but it keeps returning document containing $first
How do i select the document with minimum value?
Note : i will like to return the whole document including the "CreatedAt" "UpdatedAt", and _id. of the document containing the minimum value
Expected Result should look like:
{
"minFee_doc": {
"_id": "61e84c9f622642463640e05c",
"createdAt": "2022-01-19T17:38:39.034Z",
"updatedAt": "2022-04-24T14:48:38.100Z",
"__v": 0,
"one": 2
},
"minFee": 2,
"firstFee": 3,
"lastFee": 5,
"maxFee": 6
}
Edit: also to provide a single document not multiple
$push all docs in $group then $set the array with $filter
db.collection.aggregate([
{
$match: {}
},
{
$group: {
_id: null,
minFee_doc: { $push: "$$ROOT" },
minFee: { $min: { $toDouble: "$one" } },
firstFee: { $first: "$one" },
lastFee: { $last: "$one" },
maxFee: { $max: { $toDouble: "$one" } }
}
},
{
$set: {
minFee_doc: {
$filter: {
input: "$minFee_doc",
as: "m",
cond: { "$eq": [ "$$m.one", "$minFee" ] }
}
}
}
}
])
mongoplayground

How to find the latest date in nested array of objects (MongoDB)

I am trying to find the latest "order" in "orders" array in the whole collection (Not only in the one object).
Data:
[
{
_id: 1,
orders: [
{
title: 'Burger',
date: {
$date: '2021-07-18T13:12:08.717Z',
},
},
],
},
{
_id: 2,
orders: [
{
title: 'Salad',
date: {
$date: '2021-07-18T13:35:01.586Z',
},
},
],
},
];
Code:
var restaurant = await Restaurant.findOne({
'orders.date': 1,
});
Rather simple:
db.collection.aggregate([
{ $project: { latest_order: { $max: "$orders.date" } } }
])
If you like to get the full order use this:
db.collection.aggregate([
{
$project: {
latest_order: {
$first: {
$filter: {
input: "$orders",
cond: { $eq: [ "$$this.date", { $max: "$orders.date" } ] }
}
}
}
}
},
{ $sort: { "latest_order.date": 1 } },
{ $limit: 1 }
])
Mongo Playground
You have to use aggregation for that
db.collection.aggregate([
{ $unwind: "$orders" },
{ $sort: { "orders.date": -1 } },
{ $limit: 1 },
{
"$group": {
"_id": "$_id",
"orders": { "$first": "$orders" }
}
}
])
Working Mongo playground

How to find array greater than document size in MongoDB

I have schema like below
[
{
id:"111"
tags:[222,333,444,555]
},
{
id: "222"
tags:[312,345,534]
},
{
id:"333"
tags:[111,222,333,444,555]
},
]
I want to find all documents where tags array size is greater than document size returned by $match in aggregation pipeline, so in above Ex. the number of documents are 3 so i want to return all documents having tags array size greater that 3
[
{
id:"111"
tags:[222,333,444,555]
},
{
id:"333"
tags:[111,222,333,444,555]
},
]
I am using aggregation pipeline to process other info, I am stuck at how to have store document size so that i can find all tags greater than document size
below is query which i am using, i want to do it in aggregation and in one call
.aggregate([
{
"$match":{
"ids":{
"$in":[
"111",
"222",
"333"
]
}
}
})]
Facet helps you to solve this problem.
$facet helps to categorize the incoming documents. We use totalDoc for counting the document and allDocuments for getting all the documents
$arrayElemAt helps to get the first object from totalDoc where we already know that only one object should be inside the totalDoc. Because when we group it, we use _id:null
$unwind helps to de-structure the allDocuments array
Here is the code
db.collection.aggregate([
{
$facet: {
totalDoc: [
{
$group: {
_id: null,
count: {
$sum: 1
}
}
}
],
allDocuments: [
{
$project: {
tags: 1
}
}
]
}
},
{
$addFields: {
totalDoc: {
"$arrayElemAt": [
"$totalDoc",
0
]
}
}
},
{
$unwind: "$allDocuments"
},
{
$addFields: {
sizeGtDoc: {
$gt: [
{
$size: "$allDocuments.tags"
},
"$totalDoc.count"
]
}
}
},
{
$match: {
sizeGtDoc: true
}
},
{
"$replaceRoot": {
"newRoot": "$allDocuments"
}
}
])
Working Mongo playground
You can try,
$match you condition
$group by null and make root array of documents and get count of root documents in count
$unwind deconstruct root array
$match tags size and count greater than or not using $expr expression match
$replaceRoot to replace root object in root
db.collection.aggregate([
{ $match: { id: { $in: ["111", "222", "333"] } } },
{
$group: {
_id: null,
root: { $push: "$$ROOT" },
count: { $sum: 1 }
}
},
{ $unwind: "$root" },
{ $match: { $expr: { $gt: [{ $size: "$root.tags" }, "$count"] } } },
{ $replaceRoot: { newRoot: "$root" } }
])
Playground
Second option:
first 2 stages $match and $group both are same as like above query,
$project to filter root array match condition if tags size and count greater than or not, this will return filtered root array
$unwind deconstruct root array
$replaceRoot replace root object to root
db.collection.aggregate([
{ $match: { id: { $in: ["111", "222", "333"] } } },
{
$group: {
_id: null,
root: { $push: "$$ROOT" },
count: { $sum: 1 }
}
},
{
$project: {
root: {
$filter: {
input: "$root",
cond: { $gt: [{ $size: "$$this.tags" }, "$count"] }
}
}
}
},
{ $unwind: "$root" },
{ $replaceRoot: { newRoot: "$root" } }
])
Playground
You can skip $unwind and $replaceRoot stages if you want because this query always return one document in root, so you can easily access like this result[0]['root'], you can save 2 stages processing and execution time.
You could use $facet to get two streams i.e. one with the filtered documents and the counts using $count. The resulting streams can then
be aggregated further with a $filter as follows to get the desired result
db.getCollection('collection').aggregate([
{ '$facet': {
'counts': [
{ '$match': { 'id': { '$in': ['111', '222', '333'] } } },
{ '$count': "numberOfMatches" }
],
'docs': [
{ '$match': { 'id': { '$in': ['111', '222', '333'] } } },
]
} },
{ '$project': {
'result': {
'$filter': {
'input': '$docs',
'cond': {
'$gt': [
{ '$size': '$$this.tags' },
{ '$arrayElemAt': ['$counts.numberOfMatches', 0] }
]
}
}
}
} }
])

Retrieving different aggregated fields with mongoose

I am trying to wrap my head around the query which I am trying to make with mongoose on Node JS. Here is my dataset:
{"_id":{"$oid":"5e49c389e3c23a1da881c1c9"},"name":"New York","good_incidents":{"$numberInt":"50"},"salary":{"$numberInt":"50000"},"bad_incidents":"30"}
{"_id":{"$oid":"5e49c3bbe3c23a1da881c1ca"},"name":"Cairo","bad_incidents":{"$numberInt":"59"},"salary":{"$numberInt":"15000"}}
{"_id":{"$oid":"5e49c42de3c23a1da881c1cb"},"name":"Berlin","incidents":{"$numberInt":"30"},"bad_incidents":"15","salary":{"$numberInt":"55000"}}
{"_id":{"$oid":"5e49c58ee3c23a1da881c1cc"},"name":"New York","good_incidents":{"$numberInt":"15"},"salary":{"$numberInt":"56500"}}
What I am trying to do is get these values:
The most repeated city in collection
The average of bad_incidents
The maximum value of good_incidents
Maximum salary where there are no bad_incidents
I am trying to wrap my head around how I can do this in one query, because I only need one value per field. I would be glad if somebody would lead me on the right track. No need for full solution
Regards!
You may perform MongoDB aggregation with $facet operator which allows compute several aggregation at once.
db.collection.aggregate([
{
$facet: {
repeated_city: [
{
$group: {
_id: "$name",
name: {
$first: "$name"
},
count: {
$sum: 1
}
}
},
{
$match: {
count: {
$gt: 1
}
}
},
{
$sort: {
count: -1
}
},
{
$limit: 1
}
],
bad_incidents: [
{
$group: {
_id: null,
avg_bad_incidents: {
$avg: {
$toInt: "$bad_incidents"
}
}
}
}
],
good_incidents: [
{
$group: {
_id: null,
max_good_incidents: {
$max: {
$toInt: "$good_incidents"
}
}
}
}
],
max_salary: [
{
$match: {
bad_incidents: {
$exists: false
}
}
},
{
$group: {
_id: null,
max_salary: {
$max: {
$toInt: "$salary"
}
}
}
}
]
}
},
{
$replaceWith: {
$mergeObjects: [
{
$arrayElemAt: [
"$repeated_city",
0
]
},
{
$arrayElemAt: [
"$bad_incidents",
0
]
},
{
$arrayElemAt: [
"$good_incidents",
0
]
},
{
$arrayElemAt: [
"$max_salary",
0
]
}
]
}
}
])
MongoPlayground
[
{
"_id": null,
"avg_bad_incidents": 34.666666666666664,
"count": 2,
"max_good_incidents": 50,
"max_salary": 56500,
"name": "New York"
}
]

Sub group Mongoose

I'm trying to get a sub gruop from a query using nodejs and mongoose.
The thing I'm trying to do is the following:
I have this collection:
I Need to count and group all the documents with the same 'intent' and make a subgroup with the 'entity' value, so far I have this running:
try {
//We first get the total interactions from all workspace
let workspace = await Interaction.aggregate([
{ $match: { dateAdded: { $gte: todayStart, $lt: todayEnd }, workspace: workspaceID } },
{ $group: { _id: "$workspace", data: { $sum: 1 } } },
{ $sort: { _id: 1 } }
]).exec();
//We then get the total results from conversations
let results = await Interaction.aggregate([
{ $match: { dateAdded: { $gte: todayStart, $lt: todayEnd }, workspace: workspaceID } },
{ $group: { _id: '$intent', data: { $sum: 1 } } },
{ $sort: { _id: 1 } }
]).exec()
//workspaceItems = workspace.map(function (Interaction) { return Interaction._id; });
return res.json({
total: workspace,
result: results
})
} catch (err) {
console.log(err);
return res.status(500).send(err)
}
The result look like this:
{
"total": [
{
"_id": "Business",
"data": 23
}
],
"result": [
{
"_id": "N/A",
"data": 2
},
{
"_id": "PRODUCTO_BENEFICIOS",
"data": 3
},
{
"_id": "PRODUCTO_DESCRIPCION",
"data": 10
},
{
"_id": "REPORTE_TARJETA_PERDIDA",
"data": 1
},
{
"_id": "REQUISITOS",
"data": 7
}
]
}
I need the result in this way :
{
"total": [
{
"_id": "Business",
"data": 23
}
],
"result": [
{
"_id": "N/A",
"data": 2
},
{
"_id": "PRODUCTO_BENEFICIOS",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 3
},
{
"_id": "PRODUCTO_DESCRIPCION",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 10
},
{
"_id": "REPORTE_TARJETA_PERDIDA",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 1
},
{
"_id": "REQUISITOS",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 7
}
]
}
I Hope to be clear, please let me know if you know how to do this using mongoose.
Thank you in advance.
try changing the 2nd query to following
let results = await Interaction.aggregate([
{ $match: { dateAdded: { $gte: todayStart, $lt: todayEnd }, workspace: workspaceID } },
{ $group: { _id: '$intent', entities: {$push: "$entity"}, data: { $sum: 1 } } },
{ $sort: { _id: 1 } }
]).exec()
if you want a unique list of entities you can use $addToSet instead of $push

Resources