Fetching total count of matching items in paged Mongo aggregate query - javascript

I've seen basic questions on SO but not able to make it work in my case. Here's my query:
return Order.aggregateAsync([{
$match: { status: { $ne: 'incomplete' } }
}, {
$unwind: '$items'
}, {
$match: {... }
},
{
$limit: limit
}, {
$skip: skip
}, {
$group: {
_id: '$items._id',
product: {
$first: '$items.product'
},
qty: {
$first: '$items.qty'
},
ordered: {
$first: '$ordered'
}
}
}, {
$sort: { 'ordered': -1 }
},
])...
How can I return from this something such as:
{
items: [array of items by page/limit],
total: total items in db that match $match
}
I've tried adding this after $match:
{
$group: {
_id: null,
items: { $push: '$items' },
count: { $sum: 1 }
}
},
But it seems to not use limit then.
Sample data:
[{
_id:ObjectID
status: 'incomplete'
ordered: Date,
items: [{
_id: ObjectID
qty: 100,
product: ObjectID
}, {
_id: ObjectID
qty: 10,
product: ObjectID
}]
}, {
_id:ObjectID
status: 'incomplete'
ordered: Date,
items: [{
_id: ObjectID
qty: 200,
product: ObjectID
}]
}]
I want to return all the items, grouped by items as a paged query (limit,skip):
[{
_id: ObjectID
qty: 100,
product: ObjectID
}, {
_id: ObjectID
qty: 10,
product: ObjectID
}, {
_id: ObjectID
qty: 200,
product: ObjectID
}]
As well as the total count (3) of all items that match.
So:
result = {
docs: items array above,
total: 3

you try this query , its useful to you , and it will give you result for u want
db.getCollection('collectionName').aggregate([
{ $unwind: '$items'},
{
$group:
{
_id: "",
"doc": { $push: "$items" },
"total":{ $sum: 1}
}
},
{ $project : { doc : 1 , total : 1,_id:0 } }
])
output
{
"doc" : [
{
"_id" : 11,
"qty" : 100,
"product" : 12
},
{
"_id" : 111,
"qty" : 10,
"product" : 112
},
{
"_id" : 21,
"qty" : 100,
"product" : 22
},
{
"_id" : 211,
"qty" : 10,
"product" : 212
}
],
"total" : 4.0000000000000000
}

Related

How to perform aggregate query in mongooose

I have the following data,
[ { type: 'sale' , amount: 2000 }, { type: 'expenditure' , amount: 1300 }, { type: 'sale' , amount: 4090 }, { type: 'expenditure' , amount: 3000 }]
The output expected,
[ { type: 'sale' , amount: 6090 }, { type: 'expenditure' , amount: 4300 }]
I mean I want to perform the summation of 2 types 'sale' and 'expenditure' and I tried as bellow,
`
store.aggregate(
[
{
$group: {
type: "sale",
total: {
$sum: "amount"
}
}
}
],
function(err, result) {
if (err) {
res.send(err);
} else {
res.json(result);
}
}
);
});
`
store.aggregate(
[
{
$group: {
type: "sale",
total: {
$sum: "amount"
}
}
}
],
function(err, result) {
if (err) {
res.send(err);
} else {
res.json(result);
}
}
);
});
`
[ { type: 'sale' , amount: 2000 }, { type: 'expenditure' , amount: 1300 }, { type: 'sale' , amount: 4090 }, { type: 'expenditure' , amount: 3000 }]
The output expected,
[ { type: 'sale' , amount: 6090 }, { type: 'expenditure' , amount: 4300 }]
I mean I want to perform the summation of 2 types 'sale' and 'expenditure' and I tried as bellow,
You can write group like this
$group: {
_id: "$type",
type: "$type",
amount: {
$sum: "$amount"
}
}

MongoDB update value of deeply nested array and sort

I have this document of timestamps sorted by time:
{
_id: '1',
timestamps: [
{
id: '589b32cf-28b3-4a25-8fd1-5e4f86682199',
time: '2022-04-13T19:00:00.122Z'
},
{
id: '781a47de-d21a-4c2c-9814-b46f4a3cfa30',
time: '2022-04-13T20:00:00.498Z'
}
]
};
I want to update a timestamp by id, change the time, and sort
example: update timestamp with id: '589b32cf-28b3-4a25-8fd1-5e4f86682199':
{
id: '589b32cf-28b3-4a25-8fd1-5e4f86682199',
time: '2022-04-13T20:30:00.122Z'
}
the updated document should like this:
{
_id: '1',
timestamps: [
{
id: '32bb3-2222-1111-j878-b4000a3wwa30',
time: '2022-04-13T19:30:00.122Z'
},
{
id: '589b32cf-28b3-4a25-8fd1-5e4f86682199',
time: '2022-04-13T20:30:00.122Z'
}
]
};
I came up with this method to update and sort each element in the array:
const updateResult = await TimestampCollection.updateOne(
{ _id: '1' },
{
$push: {
timestamps: {
$each: [{ id: timestamp.id, time: timestamp.time }],
$sort: { time: 1 }
}
}
}
);
However this pushed a new object to the array with the same id and updated time:
{
_id: '1',
timestamps: [
{
id: '589b32cf-28b3-4a25-8fd1-5e4f86682199',
time: '2022-04-13T19:00:00.122Z'
},
{
id: '32bb3-2222-1111-j878-b4000a3wwa30',
time: '2022-04-13T19:30:00.122Z'
},
{
id: '589b32cf-28b3-4a25-8fd1-5e4f86682199',
time: '2022-04-13T20:30:00.122Z'
}
]
};
Any idea how to fix this?
I didn't find a way to do this in a single query. Sadly combining $set and $push in a single query leads to a conflict...
db.collection.update({
_id: "1",
"timestamps.id": timestamp.id
},
{
"$set": {
"timestamps.$.time": timestamp.time
},
});
db.collection.update({
_id: "1",
},
{
"$push": {
"timestamps": {
"$each": [],
"$sort": {
"time": 1
}
}
}
})
This solution involves two queries. One for updating the element in the array and the other for sorting the array.

Find number of objects in nested array with different query params

I have this Event array but can't figure out how to query into the 'guests' nested array and do two things.
count the number of guests
count the number of attended guests (marked 'Y')
{ _id: new ObjectId('1'),
name: event1,
guests: [
{
phone: +12222222222,
_id: new ObjectId,
attended: 'Y'
},
{
phone: +12344466666,
_id: new ObjectId,
attended: 'Y'
},
{ phone: +11234567890,
_id: new ObjectId,
attended:
},
{
phone: +14443332222,
_id: new ObjectId,
attended: 'Y'
},
{ phone: +19090909090,
_id: new ObjectId
attended:
}
],
},
{ _id: new ObjectId('2'),
name: event2,
guests: [
{
phone: +11111111111,
_id: new ObjectId,
attended:
},
{
phone: +12222222222,
_id: new ObjectId,
attended: 'Y'
},
{
phone: +133333333333,
_id: new ObjectId,
attended: 'Y'
}
],
},
My code below is on its 20th iteration without getting any closer.
const event = await Event.findById(req.params.id);
For MongoDB query, you can use $size for calculating the size of the array field and $filter to filter specific document(s) that matched the criteria in the projection.
db.collection.find({
_id: "1"
},
{
_id: 1,
name: 1,
guests: 1,
totalGuests: {
$size: "$guests"
},
attendedGuests: {
$size: {
"$filter": {
input: "$guests",
cond: {
$eq: [
"$$this.attended",
"Y"
]
}
}
}
}
})
Sample Mongo Playground
Yong Shun helped above but didn't get 100% of the way -- here is the full answer in case anyone encounters something like this in the future:
module.exports.showEvent = async (req, res) => {
const event = await Event.findById(req.params.id).populate('artist');
const { guest_id } = req.cookies;
let totalGuests = 0;
let attendedGuests = 0;
const eventData = await Event.aggregate([
{
"$match": {
"_id": objectId(req.params.id)
}
},
{
$project: {
_id: 1,
name: 1,
guests: 1,
totalGuests: { $cond: { if: { $isArray: "$guests" }, then: { $size: "$guests" }, else: "NA" } },
attendedGuests: {
$size: {
$filter: {
input: "$guests",
as: "guest",
cond: {
$and: [{
$eq: ["$$guest.attended", "Y"]
}]
}
}
}
}
}
}
])
if (eventData && Array.isArray(eventData) && eventData.length > 0) {
totalGuests = eventData[0].totalGuests;
attendedGuests = eventData[0].attendedGuests;
}
if (!event) {
req.flash('error', 'Cannot find that Event');
return res.redirect('/events');
}
res.render('events/show', { event, eventData });
console.log(totalGuests, attendedGuests);
};

Mongo Query - match and lookup combined

I’ve defined the following query which fetches me all items with an id which is in a given list of ids, and a status of either active or retracted.
const query = {
$and : [
{
$or: [
{
status: ‘active’,
},
{
status: ‘retracted’,
},
],
},
{
id: { $in: ids },
},
],
};
Each of these items has a parent_id field, which can either be null if the item does not have a parent, or can be the id of the parent.
I want my query to fetch all items with the ids I supply, as well as their parent items, if such a parent exists.
For example, if I supply the following IDs
[1,2,3]
and item 2 has a parent with id 5, while item 1 and 3 have parent_id set to null, I want my query to return the following items:
[1,2,3,5].
To achieve this I wrote the following query:
const collection = db.collection(‘myCollection’);
const data = await collection.aggregate([
{$match : query},
{
$lookup: {
from: ‘myCollection’,
let: { parentID: ‘$parent_id’},
pipeline: [
{
$match: {
$expr: {
$eq: [‘$id’, ‘$$parentID’],
},
},
},
as: ‘parent’,
},
},
]).sort(‘created_date’, ‘desc’).toArray();
return data;
However, this always returns null.
Sample Data:
[
{
id: 1,
parent_id: 3,
data: ‘bla bla’
},
{
id: 2,
parent_id: null,
data: ‘bla bla bla’
},
{
id: 3,
parent_id: null,
data: ‘bla’
}
]
Input: [1]
Output:
[
{
id: 1,
parent_id: 3,
data: ‘bla bla’
},
{
id: 3,
parent_id: null,
data: ‘bla’
}
]
The approach with $lookup being run upon same collection should work however it gives you a nested array so you need few additional stages to flatten such array and get all elements as on result set:
db.collection.aggregate([
{
$match: { id: { $in: [1] } }
},
{
$lookup: {
from: "collection",
localField: "parent_id",
foreignField: "id",
as: "parent"
}
},
{
$project: {
all: {
$concatArrays: [
"$parent",
[ "$$ROOT" ]
]
}
}
},
{
$project: {
"all.parent": 0
}
},
{
$unwind: "$all"
},
{
$replaceRoot: {
newRoot: "$all"
}
}
])
Mongo Playground
Your aggregation was malformed and lack some "]" for example closing the pipeline fied.
If you fix that the query works fine for me. Example
You can try this. The input array is [2,3] where 2 has parent id=1 and that is not in the input array. But the output array has the entry.
Working Playground
db.collection.aggregate([
{
$match: {
_id: {
$in: [
2,
3
]
}
}
},
{
$lookup: {
from: "collection",
localField: "p",
foreignField: "_id",
as: "parent"
}
},
{
$project: {
_id: 0,
id: {
$concatArrays: [
[
"$_id"
],
"$parent._id"
]
}
}
},
{
$unwind: "$id"
},
{
$sort: {
id: 1
}
}
])

Trying to get data from Mongo DB with aggregate

I have "Offers" and "Requests" collections, I need to get all offers that user made, group them by requests and find the lowest "Offer.price" on each request, each offer has requestId field.
I am using aggregate to solve this,
db.Offer.aggregate([{
$match: {
ownerId: mongoose.Types.ObjectId(req.params.ownerId)
}
},
{
$group: {
_id: "$requestId",
price: {
$min: "$price"
}
}
}
])
and This is what i get :
[ { _id: 5dc47241af1406031489c65c, price: 14 },
{ _id: 5dc47241af1406031489c653, price: 3 },
{ _id: 5dc47241af1406031489c656, price: 5 },
{ _id: 5dc8add63f73953ff408f962, price: 6 },
{ _id: 5dc8add63f73953ff408f969, price: 22 },
{ _id: 5dc47241af1406031489c658, price: 1 } ]
Now I want to populate these with rest of the data from "Offer"
const OfferSchema = new Schema({
requestId: {
type: Schema.Types.ObjectId,
ref: 'Request'
},
ownerId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'User'
},
price: {
type: Number,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
isBest: {
type: Boolean,
default: false
},
isWinner: {
type: Boolean,
default: false,
}
});
What would be best way to do something like this?
Thank you for your help!
Consider the following dataset:
db.dum.insert({ownerId:1, requestId:'a', price:3, createdAt:3, isWinner:true})
db.dum.insert({ownerId:1, requestId:'a', price:1, createdAt:1, isWinner:false})
db.dum.insert({ownerId:1, requestId:'a', price:2, createdAt:2, isWinner:true})
db.dum.insert({ownerId:1, requestId:'b', price:4, createdAt:2, isWinner:true})
db.dum.insert({ownerId:1, requestId:'b', price:5, createdAt:1, isWinner:false})
db.dum.insert({ownerId:2, requestId:'b', price:5, createdAt:1, isWinner:false})
You could use $reduce
Here, for a grouping id, we keep all matching documents as an array (candidates).
On the project stage, for each group we iterate through the array, and reduce it to the minimal element found (by price that is)
db.dum.aggregate([{
$match: {
ownerId: 1
}
},
{
$group: {
_id: "$requestId",
candidates: { $push:'$$ROOT'}
}
},
{
$project:{
item: {
$reduce: {
input: '$candidates',
initialValue: '$candidates.0',
in: {
$cond: {
if: {
$lt: ['$$value.price', '$$this.price']
},
then:'$$value',
else:'$$this'
}
}
}
}
}
},
{
$replaceRoot:{newRoot:'$item'}
}
]).toArray()
output:
[
{
"_id" : ObjectId("5ddcc8e0eb1f0217802fb507"),
"ownerId" : 1,
"requestId" : "b",
"price" : 4,
"createdAt" : 2,
"isWinner" : true
},
{
"_id" : ObjectId("5ddcc8e0eb1f0217802fb505"),
"ownerId" : 1,
"requestId" : "a",
"price" : 1,
"createdAt" : 1,
"isWinner" : false
}
]

Categories

Resources