Trying to get data from Mongo DB with aggregate - javascript

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
}
]

Related

Mongo Aggregation pipeline update or push

I have a MongoDB Model which consist of array of members as obejcts.
const guestSchema = new mongoose.Schema({
salutation: {
type: String,
},
members: [membersSchema],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
},
});
Members Schema:
const membersSchema = new mongoose.Schema({
name: String,
status: {
type: String,
enum: ['regular', 'helper'],
default: 'regular',
},
});
I want to achieve of doing an update in case documet with given ID exist or push to an array in case ID with document in array does not exist. I use aggregation pipeline, however I am not able to achieve pushing new document to array. Why can't I use push after else statement like this.
const subDocumentToUpsert = { 'name': mem.name, 'status': mem.status, '_id': ObjectId(mem.id)}
const subDocumentNoID = { 'name': mem.name, 'status': mem.status}
await Guest.findOneAndUpdate(
{ "_id": req.params.id },
[
{
$set: {
members: {
$cond: {
if: { $in: [subDocumentToUpsert._id, '$members._id'] },
then: {
$map: {
input: '$members',
as: 'sub_document',
in: {
$cond: {
if: { $eq: ['$$sub_document._id', subDocumentToUpsert._id] },
then: subDocumentToUpsert,
else: '$$sub_document',
},
},
},
},
else: {
$push: {
subDocumentNoID
},
},
},
},
},
},
},
]);
What is the best way of doing so? Thank you
You can do as follow:
db.collection.update({
_id: {
$in: [
1,
2
]
}
},
[
{
$set: {
members: {
$cond: {
if: {
$in: [
5,
"$members._id"
]
},
then: {
$map: {
input: "$members",
as: "sub",
in: {
$cond: {
if: {
$eq: [
"$$sub._id",
5
]
},
then: {
_id: 5,
status: "regular_updated",
name: "Negan_updated"
},
else: "$$sub"
},
},
},
},
else: {
$concatArrays: [
"$members",
[
{
_id: 5,
status: "regular_upserted",
name: "Negan_upserted"
}
]
]
}
}
}
}
}
}
],
{
multi: true
})
Explained:
Check if _id:5 exist in the subobject and update via $map/$cond only the object that has the _id:5.
In case there is no _id:5 add the new object to the array with $concatArrays.
Playground

How to aggregate a nested array using MongoDB?

I tried to use aggregate to find out each product's monthly sales in my order , but I ran into a problem.
Here's my data structures.
Order.model.ts
const OrderSchema: Schema = new Schema(
{
userId: {
type: String,
require: true,
},
products: [
{
product: {
_id: {
type: String,
},
title: {
type: String,
},
desc: {
type: String,
},
img: {
type: String,
},
categories: {
type: Array,
},
price: {
type: Number,
},
createdAt: {
type: String,
},
updatedAt: {
type: String,
},
size: {
type: String,
},
color: {
type: String,
},
},
quantity: {
type: Number,
default: 1,
},
},
],
quantity: {
type: Number,
},
total: {
type: Number,
},
address: {
type: String,
require: true,
},
status: {
type: String,
default: 'pending',
},
},
{ timestamps: true },
);
Order-service.ts
public async getIncome(productId?: string) {
const date = new Date();
const lastMonth = new Date(date.setMonth(date.getMonth() - 1));
const previousMonth = new Date(new Date().setMonth(lastMonth.getMonth() - 1));
//const lastYear = new Date(date.setFullYear(date.getFullYear() - 1));
const income = await this.order.aggregate([
{
$match: {
createdAt: { $gte: lastMonth },
...(productId && {
products: { $elemMatch: { product: { _id: productId } } },
}),
},
},
{
$project: {
month: { $month: '$createdAt' },
sales: '$total',
},
},
{
$group: {
_id: '$month',
total: { $sum: '$sales' },
},
},
]);
return income;
}
When I calculate whole sales without productId , it went well , I tried to use elemMatch to find productId , but it won't work , did I miss something ?
Try $unwind "products" array first, then apply $match:
const income = await this.order.aggregate([
{
$unwind: '$products'
},
{
$match: {
createdAt: { $gte: lastMonth },
product: { _id: productId },
},
},
{
$project: {
month: { $month: '$createdAt' },
sales: '$total',
},
},
{
$group: {
_id: '$month',
total: { $sum: '$sales' },
},
},
]);

How to do a full match search with two parameters?

I have database model
Schema({
members: [
{
type: String,
required: true,
ref: "User"
}
],
createdAt: {
type: Date,
default: Date.now(),
required: true
},
lastMessage: {
message: {
type: String,
required: true
},
from: {
type: String,
required: true
},
createdAt: {
type: Date,
required: true
}
},
messages: [
{
createdAt: {
type: Date,
required: true
},
message: {
type: String,
required: true
},
from: {
type: String,
ref: "User",
required: true
}
}
]
});
That code I use to find some documents
Chats.countDocuments(
{
members: {
$in: ["userIdOne", "userIdTwo"]
}
},
cb
)
I use $in to find data what I need, but $in find all documents which contains one of this userId.... Me need to find only one document which contains that user's ids.
How can i do this?
Use $all instead
Chats.countDocuments(
{
members: {
$all: ["userIdOne", "userIdTwo"]
}
},
cb
)
If you need exact match, you can use $setEquals aggregation operator.
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
{
$setEquals: [
"$members",
[
"userIdOne",
"userIdTwo"
]
]
},
true
]
}
}
},
{
$count: "count"
}
])
Input:
[
{
"members": [
"userIdOne"
]
},
{
"members": [
"userIdTwo"
]
},
{
"members": [
"userIdOne",
"userIdTwo"
]
},
{
"members": [
"userIdOne",
"userIdTwo",
"userIdThere"
]
}
]
Output:
[
{
"count": 1
}
]
Playground
You can integrate this to mongoose like this:
const result = await Chats.aggregate([
{
$match: {
$expr: {
$eq: [
{
$setEquals: ["$members", ["userIdOne", "userIdTwo"]]
},
true
]
}
}
},
{
$count: "count"
}
]);
const count = result[0].count;

Mongo DB $push object wraps it in an array first

When I use $push in mongodb, the expected outcome turns out differently. It actually wraps the object I want to push in an array. This is problematic because I'd have to map over the result just to extract it. Any help would be greatly appreciated.
My Mongo Query
const pushAction = {
$push: {
cart: {
id: itemId,
quantity: quantity
}
}
}
// Add item to user's cart
User.update({_id: userId}, pushAction, (err, success) => {
if (err) {
res.status(422).json({'error': 'There was a problem adding the item to your cart.'});
}
if (success) {
// Find user and return the cart
User.findOne({_id: userId}, {cart: 1}, (err, user) => {
res.status(200).json({'message': 'The item was successfully added to your cart.', cart: user.cart});
})
}
});
User Schema
// Define User Model
const userSchema = new Schema({
firstName: {
type: Schema.Types.String,
required: true
},
lastName: {
type: Schema.Types.String,
required: true
},
password: {
type: Schema.Types.String,
required: true
},
email: {
type: Schema.Types.String,
required: true
},
cart: {
type: Schema.Types.Array
},
dateCreated: {
type: Schema.Types.Date,
default: Date.now,
required: true
},
dateUpdated: [
{
date: {
type: Schema.Types.Date
},
details: {
type: Schema.Types.ObjectId
}
}
],
verified: {
type: Schema.Types.Boolean,
required: true
},
role: {
type: Schema.Types.String,
default: ROLES_BASIC_USER
}
});
Expected Outcome
"cart" : [
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
},
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
},
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
}
],
Actual Result
"cart" : [
[
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
}
],
[
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
}
],
[
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
}
]
]
// Define User Model
const userSchema = new Schema({
firstName: {
type: Schema.Types.String,
required: true
},
lastName: {
type: Schema.Types.String,
required: true
},
password: {
type: Schema.Types.String,
required: true
},
email: {
type: Schema.Types.String,
required: true
},
cart:[ {
id: Schema.Types.ObjectId,
quantity: Number
}],
dateCreated: {
type: Schema.Types.Date,
default: Date.now,
required: true
},
dateUpdated: [
{
date: {
type: Schema.Types.Date
},
details: {
type: Schema.Types.ObjectId
}
}
],
verified: {
type: Schema.Types.Boolean,
required: true
},
role: {
type: Schema.Types.String,
default: ROLES_BASIC_USER
}
});
Try changing pushAction as follows:
const pushAction = {
$push: {
cart: { $each: [ {id: itemId, quantity: quantity } ] }
}
}
Clean existing items in cart field before trying this.
If it still fails then the issue might be with the schema.

Fetching total count of matching items in paged Mongo aggregate query

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
}

Categories

Resources