Mongoose: Sum of all points queried by a particular user - node.js

I have a collection in Mongoose called Points where I have a history of all points of a user
This is the point schema
const PointSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
reason: {
type: String,
required: true,
},
points: {
type: Number,
required: true,
},
time: {
type: Date,
default: Date.now,
},
});
I want a JSON output like this
{
"points": 30,
"history": [
{ Point object },
{ Point object },
{ Point object },
]
}
How can I get the sum of all the points queried by a particular user, and get the output as above if I have the particular user's id?

You could do a simple aggregation where you first $match the user-id, then $sum all the points and finally push each document to the history array:
db.collection.aggregate([
{
"$match": {
user: "<user-id-to-match>"
}
},
{
"$group": {
"_id": "$user",
"points": {
$sum: "$points"
},
history: {
"$push": "$$ROOT"
}
}
},
{
$project: {
_id: 0
}
}
])
Here's an example I've created on mongoplayground: https://mongoplayground.net/p/Q_TtcI_dkZu

Related

populate a field in aggregation result

const commentSchema = new mongoose.Schema({
name: {
type: String,
default: "anonymous",
},
comment: {
type: String,
required: true,
},
post: {
type: mongoose.Schema.Types.ObjectId,
ref: "Post",
required: true,
},
}, {
timestamps: true
});
const trending = await Comment.aggregate([{
$group: {
_id: "$post",
total: {
$sum: 1
},
},
}, ]);
Result of aggregation
[{
"_id": "61eb55808551961dc737c00c",
"total": 5
},
{
"_id": "61eb4a490a894ac62bd833ab",
"total": 2
}
]
hello, please is there a way i can populate the field _id after using aggregation, i want to get all the fields of post not just the _id?

MongoDB, sort the results by the number of matching elements between two arrays

In my project I use MongoDB as a database (specifically the mongoose driver for typescript, but this shouldn't matter) and I have a collection of posts that follow this schema:
export const PostSchema = new Schema({
author: { type: Types.ObjectId, required: true, ref: 'User' },
text: { type: String, required: true },
tags: [{ type: Types.ObjectId, required: true, ref: 'Tag' }],
location: { type: PointSchema, required: true },
}
export const PointSchema = new Schema({
type: {
type: String,
enum: ['Point'],
required: true,
},
coordinates: {
type: [Number],
required: true,
},
locationName: {
type: String,
required: true,
},
});
My question is if it is possible to write a query (I think an aggregation is needed) that returns all posts that meet a condition (such as that the position must be at a specific distance) and orders the results by a specific array of tags passed as an argument (in my case the array varies from user to user and represents their interests).
That is, choosing the array ["sport", "music", "art"] as an example, I would like a query that retrieves from the database all the posts that meet a certain condition (irrelevant in this question) and orders the results so that first are documents whose array of tags share elements with the array ["sport", "music", "art"], and only at the end the documents without any correspondence.
That is, something like this:
[
{
_id: "507f191e810c19729de860ea",
tags: ["sport", "art", "tennis"] // 2 matches
},
{
_id: "507f191e810c1975de860eg",
tags: ["sport", "food"] // 1 matches
},
{
_id: "607f191e810c19729de860ea",
tags: ["animals", "art"] // 1 matches
},
{
_id: "577f191e810c19729de860ea",
tags: ["animals", "zoo"] //0 matches
}
]
if your collection looks like this:
[
{
"author": "John",
"tags": [
ObjectId("60278ce8b370ff29b83226e2"), // Sport
ObjectId("60278ce8b370ff29b83226e8"), // Music
ObjectId("60278ce8b370ff29b83226e5"), // Food
]
},
{
"author": "Dheemanth",
"tags": [
ObjectId("60278ce8b370ff29b83226e7"), // Tech
ObjectId("60278ce8b370ff29b83226e5"), // Food
ObjectId("60278ce8b370ff29b83226e2") // Sport
]
},
{
"author": "Niccolo",
"tags": [
ObjectId("60278ce8b370ff29b83226e2"), // Sport
ObjectId("60278ce8b370ff29b83226e8"), // Music
ObjectId("60278ce8b370ff29b83226e3") // Art
]
}
]
then this is the solution:
db.posts.aggregate([
{
$lookup: {
from: "tags",
let: { "tags": "$tags" },
pipeline: [
{
$match: {
$expr: { $in: ["$_id", "$$tags"] }
}
}
],
as: "tags"
}
},
{
$addFields: {
"tagCount": {
$size: {
$filter: {
input: "$tags",
as: "tag",
cond: { $in: ["$$tag.name", ["sport", "music", "art"]] }
}
}
}
}
},
{
$sort: { tagCount: -1 }
},
{
$project: {
_id: 1,
tags: "$tags.name"
}
}
])
Output:
[
{
"_id": ObjectId("60278e14b370ff29b83226eb"),
"tags": ["sport", "art", "music"]
},
{
"_id": ObjectId("60278e14b370ff29b83226e9"),
"tags": ["sport", "food", "music"]
},
{
"_id": ObjectId("60278e14b370ff29b83226ea"),
"tags": ["sport", "food", "tech"]
}
]

Count by category and sum it up in MongoDB

I have a product collection in MongoDb which sole fields like _id, category, user_id.
I want to check and count the sum number of each category in collection given the matching the user_id and then sum up all the count again at the end.
my solution is :
return Product.aggregate([
{ $match: { "user_id": "id if user that added the product" } },
{ "$unwind": "$category" },
{
"$group": {
"_id": {
'category': '$category',
},
"count": { "$sum": 1 }
}
},
{ "$sort": { "_id.category": 1 } },
{
"$group": {
"_id": "$_id.category",
"count": { "$first": "$count" }
}
}
])
the code gives me the count of each category without matching the condition of user_id. But when I add the $match it fails.
Product Schema:
const ProductSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
quantity: {
type: Number,
default: -1
},
category:
{
type: String,
required: true
},
manufactured_by: {
type: String,
required: true
},
user_id: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
}
})
my result if I dont add the condition:
[
{
"_id": "A Tables",
"count": 1
},
{
"_id": "C Tables",
"count": 4
},
{
"_id": "B Tables",
"count": 2
}
]
Not sure what you are trying to achieve from the last stage in your pipeline.
But the following should give you desired output (without any complications that you added)
async getSellerStatistics(seller_id) {
return await Product.aggregate([
{ $match: { user_id: seller_id } }
{ $unwind: "$category" },
{
$group: {
_id: "$category",
count: { $sum: 1 },
},
},
{ $sort: { _id: 1 } },
])
}

Get the average value with mongoose query

I'm getting a list of 'spot' with mongoose filtered by location and other stuff, with the code below which works fine.
But I want the value of 'rate' to be a $avg (average) of all reviews and not the list of the reviews. It's an aggregation of another collection.
this is what I get:
{
"_id":"5f0ade7d1f84460434524d3d",
"name":"Fly",
...
"rate":[
{"_id":"5f0bfca64ca1cc02ffe48faf","spot_id":"5f0ade7d1f84460434524d3d","rate":3},
{"_id":"5f0bfdb44ca1cc02ffe48fb0","spot_id":"5f0ade7d1f84460434524d3d","rate":2},
{"_id":"5f0bfdb44ca1cc02ffe48fb1","spot_id":"5f0ade7d1f84460434524d3d","rate":1}
]
},
but I would like this kind of result:
{
"_id":"5f0ade7d1f84460434524d3d",
"name":"Fly",
...
"rate": 2
},
I tried many different things, and I guess I need to use $group but can't figure out how to get the right output.
the reviews schema:
const reviewsSchema = new mongoose.Schema({
_id: {
type: ObjectId,
required: true,
},
spot_id: {
type: ObjectId,
required: true,
},
rate: {
type: Number,
required: true,
},
})
the spot Schema
const spotsSchema = new mongoose.Schema({
_id: {
type: ObjectId,
required: true,
},
name: {
type: String,
required: true,
},
...
})
The code:
Spots.aggregate
([
{
$geoNear: {
near: { type: "Point", coordinates: [ parseFloat(longitude), parseFloat(latitude) ] },
distanceField: "location",
maxDistance: parseInt(distance) * 1000,
query: {
$and: [
{ $or : filter },
{ $or : closed },
{ published: true }
]
}
}
},
{ $match: {} },
{
$lookup:{
from: 'reviews',
localField: '_id',
foreignField: 'spot_id',
as: 'rate'
}
},
])
You're really close, you just have to actually calculate the avg value which can be done using $map and $avg like so:
{
$addFields: {
rate: {
$avg: {
$map: {
input: "$rate",
as: "datum",
in: "$$datum.rate"
}
}
}
}
}
MongoPlayground

Check if sub document is found in $project

I am trying to find all the trips provided by a company, grouped by the driver of the bus, and check if a given passenger was part of the trip.
Content is an array that can have reference to multiple models: User, Cargo, etc.
I can somewhat achieve my desired result using:
traveled: { $in: [ passengerId, "$content.item" ] },
But i want to confirm that the matched id is indeed a 'User'(passenger). I have tried:
traveled: { $and: [
{ $in: [ passengerId, "$content.item" ] },
{ $in: [ `Passenger`, "$content.kind" ] },
]},
But it also matches if the passed id has a kind of 'Cargo' when there is another content with a kind of 'User' is inside the array.
// Trip
const schema = Schema({
company: { type: Schema.Types.ObjectId, ref: 'Company', required: false },
driver: { type: Schema.Types.ObjectId, ref: 'User', required: true },
description: { type: String, required: true },
....
content: [{
kind: { type: String, required: true },
item: { type: Schema.Types.ObjectId, refPath: 'content.kind', required: true }
}]
});
const Trip = mongoose.model('Trip', schema, 'trips');
Trip.aggregate([
{ $match: { company: companyId } },
{
$project: {
_id: 1,
driver: 1,
description: 1,
traveled: { $in: [ passengerId, "$content.item" ] },
// traveled: { $and: [
// { $in: [ passengerId, "$content.item" ] },
// { $in: [ `Passenger`, "$content.kind" ] },
// ]},
}
},
{
$group : {
_id: "$driver",
content: {
$push: {
_id: "$_id",
description: "$description",
traveled: "$traveled",
}
}
},
}
]).then(console.log).catch(console.log);
There is no $elemMatch operator in $project stage. So to use mimic similar functionality you can create $filter with $size being $gt > 0.
Something like
"traveled":{
"$gt":[
{"$size":{
"$filter":{
"input":"$content",
"as":"c",
"cond":{
"$and":[
{"$eq":["$$c.item",passengerId]},
{"$eq":["$$c.kind","User"]}
]
}
}
}},
0
]
}

Resources