How to sort result by number of refs in MongoDB? - mongodb

I'm creating an inventory application, in which items can be added to an inventories. Items and inventories share a many-to-many relationship. These are my schemas for both:
const itemSchema = mongoose.Schema({
name: {
type: String,
required: true,
},
emoji: {
type: String,
required: true,
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
default: null,
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category',
},
});
const inventorySchema = mongoose.Schema({
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
access: {
type: String,
enum: ['PRIVATE', 'PUBLIC'],
default: 'PRIVATE',
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category',
},
items: [
{
item: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Item',
},
quantity: {
type: Number,
default: 0,
},
},
],
sharedWith: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Group',
},
],
});
What I require...
I basically want to apply a sort to return me all the items in the order from most added to least added (in all Inventories).
I can create a counter field inside the Item Model and update it every time an item is added or removed from an inventory, but it does not seem like the solution for this. Any help would be appreciated!

You did not provide sample documents but is sounds like this should work:
Keep only necessary fields
$unwind so each item in each inventory is a document
$group items by _id and count the quantity for each.
Format the data and sort by count
db.inventories.aggregate([
{$project: {items: 1, _id: 0}},
{$unwind: "$items"},
{$group: {_id: "$items.item", count: {$sum: "$items.quantity"}}},
{$lookup: {
from: "items",
localField: "_id",
foreignField: "_id",
as: "item"
}
},
{$project: {item: {$mergeObjects: [{$first: "$item"}, {count: "$count"}]}}},
{$replaceRoot: {newRoot: "$item"}},
{$sort: {count: -1}}
])

Related

Mongodb get top 5 grouped by object

I am using a MERN stack and need to group by certain attribute of schema, so for example: this is my schema,
const likeSchema = new mongoose.Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'User'
},
commentId: {
type: Schema.Types.ObjectId,
ref: 'Comment'
},
questionId: {
type: Schema.Types.ObjectId,
ref: 'Question'
}
}, { timestamps: true })
Now, I want to group by questionId and then return the 5 groups with the highest count. So in essence, I want the top 5 posts with the most likes. Is there a way to do this with mongodb?
Any help is appreciated!
You can first group by questionId, then sort the result and select the top 5 records.
db.collection.aggregate([{
"$group": {
_id: "$questionId",
userId: {
$first: "$userId"
},
commentId: {
$first: "$commentId"
},
questionId: {
$first: "$questionId"
},
count: {
$sum: 1
}
},
},
{ $sort: { "count": -1 } },
{ $limit: 5 },
])

Mongodb aggregate lookup by _id is not working

MongoDb Aggregate lookup is not producing result while foreignField as _id.
I have two collections say users and discussions
Sample users Data:
[{
_id: 5f9c50dcfac1f091400225e3,
email: 'Peter.Parker#gmail.com',
details: { fname: 'Peter Test', lname: 'Fulton' },
},
{
_id: 5fa432bfb91fab7db60c70eb,
email: 'Spidy#xxx.com',
details: { fname: 'Frodo', lname: 'Baggins' },
},
{
_id: 5fa8ec7d3ce22610e5d15190,
email: 'tommy#xxx.com',
details: { fname: 'Tommy', lname: 'test' },
},
{
_id: 5fc38bb0b3683651be970180,
email: 'jerry#xxx.io',
},
{
_id: 5fd2340cc443d155ab38383b,
email: 'Dexter#xxx.io',
details: { fname: 'Dexter', lname: 'Lab' },
}]
Sample discussions data:
{_id: ObjectId("5fb2abd6b14fa5683979df58"),
tags: [ 'javascritp', 'css', 'html' ],
title: 'Why is this inline-block element pushed downward?',
post: 'Test Post',
learnerId: ObjectId("5f9c50dcfac1f091400225e3"),
}
Here '_id' of users is linked with 'learnerId' of 'discussions'.
My Aggregate query is like below.
db.users.aggregate([
{ $project: { "details.fname": 1, "details.lname":1,email:1, _id:1}},
{$lookup: {
from: "discussions",
localField: "learnerId",
foreignField: "_id",
as: "discussions"
}}
])
Here 'Peter Test' with _id 5f9c50dcfac1f091400225e3 linked with discussions LeanerId. But I expected discussions will populate in my result. My am seeing empty discussions array in all users collections.
[{
_id: 5f9c50dcfac1f091400225e3,
email: 'Peter.Parker#gmail.com',
details: { fname: 'Peter Test', lname: 'Fulton' },
discussions: []
},
{
_id: 5fa432bfb91fab7db60c70eb,
email: 'Spidy#xxx.com',
details: { fname: 'Frodo', lname: 'Baggins' },
discussions: []
},
{
_id: 5fa8ec7d3ce22610e5d15190,
email: 'tommy#xxx.com',
details: { fname: 'Tommy', lname: 'test' },
discussions: []
},
{
_id: 5fc38bb0b3683651be970180,
email: 'jerry#xxx.io',
discussions: []
},
{
_id: 5fd2340cc443d155ab38383b,
email: 'Dexter#xxx.io',
details: { fname: 'Dexter', lname: 'Lab' },
discussions: []
}]
Can you point out what wrong in my aggregate query here?
You have mismatched the localField and foreignField
db.users.aggregate([
{
$project: {
"details.fname": 1,
"details.lname": 1,
email: 1,
_id: 1
}
},
{
$lookup: {
from: "discussions",
localField: "_id",
foreignField: "learnerId",
as: "discussions"
}
}
])
Working Mongo playground

How to calculate average rating of my product in mongodb?

This is the query I used to get the average of rating, but the result was null:
db.products.aggregate([{$unwind: "$reviews"}, {$group: {_id: "$reviews", ratingAvg: {$avg: "$rating"}}}])
This is my product schema:
category: String,
name: String,
price: Number,
image: String,
description: String,
stock: Number,
reviews: [
{
type: mongoose.Schema.Types.ObjectID, ref: 'Review'
}
})
This is my review schema:
text: String,
rating: Number,
// author: {
// id: {type: mongoose.Schema.Types.ObjectID, ref: 'User'},
// name: String
// }
author: String
})
Every time I run this query I get:
{ "_id" : ObjectId("5f79d1b4b4b3c1061f1fbe52"), "ratingAvg" : null }
{ "_id" : ObjectId("5f79d1a5b4b3c1061f1fbe51"), "ratingAvg" : null }
{ "_id" : ObjectId("5f79d196b4b3c1061f1fbe4f"), "ratingAvg" : null }
It is showing ratingAvg as"null" whereas I have ratings for that product in my database.
$lookup helps to join two collections.
[
{
"$lookup": {
"from": "Review",
"localField": "reviews",
"foreignField": "_id",
"as": "reviews"
}
},
{
$unwind: "$reviews"
},
{
$group: {
_id: null,
ratingAvg: {
$avg: "$reviews.rating"
}
}
}
]
Then you can easily find the average using $avg along with $group after destructure the array using $uniwnd
Working Mongo playground

mongoose recursive nesting

in my project a user can create products. each user have a reference to all of its products and each product have a reference to its user.
both the user and the product have a 'name' field.
i need to get all of the users products array, and in that array i want to have the product name and the
user name that created it (and only those fields and no others).
for example:
Users:
{ _id: 1, name: 'josh', productIds: [1,3]}
{ _id: 2, name: 'sheldon', productIds: [2]}
Products:
{ _id: 1, name: 'table', price: 45, userId: 1}
{ _id: 2, name: 'television', price: 25 userId: 2}
{ _id: 3, name: 'chair', price: 14 userId: 1}
i want to get the following result:
{ _id: 1, name: 'josh',
products: {
{ _id: 1, name: 'table', user: { _id: 1, name: 'josh' },
{ _id: 3, name: 'chair', user: { _id: 1, name: 'josh' },
}
}
{ _id: 2, name: 'sheldon',
products: {
{ _id: 2, name: 'television', userId: { _id: 2, name: 'sheldon' }
}
}
i tried the following query that didn't fill the inner userId and left it with only the id (no name):
User.aggregate([
{
$lookup:
{
from: 'products',
localField: 'productIds',
foreignField: '_id',
as: 'products'
}
}
i also tried the following, which did the same as the first query except it only retried the first product for each user:
User.aggregate([
{
$lookup:
{
from: 'products',
localField: 'productIds',
foreignField: '_id',
as: 'products'
}
},
{
$unwind: {
path: "$products",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: "user",
localField: "products.userId",
foreignField: "_id",
as: "prodUsr",
}
},
{
$group: {
_id : "$_id",
products: { $push: "$products" },
"doc": { "$first": "$$ROOT" }
}
},
{
"$replaceRoot": {
"newRoot": "$doc"
}
}
Product:
const schema = new Schema(
{
name: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
}
);
module.exports = mongoose.model('Product', schema);
User:
const schema = new Schema(
{
name: {
type: String,
required: true,
unique: true
},
productIds: [{
type: Schema.Types.ObjectId,
ref: 'Product',
require: false
}],
{ timestamps: true }
);
module.exports = mongoose.model('User', schema);
any help will be highly appreciated
It looks like a perfect scenario for $lookup with custom pipeline and another nested $lookup. The inner one allows you to handle product-> user relationship while the outer one handles user -> product one:
db.Users.aggregate([
{
$project: {
productIds: 0
}
},
{
$lookup: {
from: "Products",
let: { user_id: "$_id" },
pipeline: [
{
$match: {
$expr: {
$eq: [ "$userId", "$$user_id" ]
}
}
},
{
$lookup: {
from: "Users",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{
$unwind: "$user"
},
{
$project: {
"user.productIds": 0,
"price": 0,
"userId": 0
}
}
],
as: "products"
}
}
])
Mongo Playground

Mongo aggregation lookup

I am trying to attempt the following
From a log, find all prizes the user entered
return the order numbers and the title
I am using the mongo aggregation framework.
I match by userId
I group the prizes by the same product and then push order numbers
I want to do a look up to the prizes collection to find the title of the prizes.
The look up is returning no elements
Here is the aggregation code
db.pointslogs.aggregate(
// Pipeline
[
// Stage 1
{
$match: {
"user": ObjectId("5aacff47c67f99103bcbf693")
}
},
// Stage 2
{
$group: {
_id: "$productPurchased",
orderNumber: { $push: "$orderNumber" }
}
},
// Stage 3
{
$unwind: {
path : "$orderNumber",
}
},
// Stage 4
{
$lookup: {
"from" : "prizes",
"localField" : "_id",
"foreignField" : "_id",
"as" : "title"
}
},
]
// Created with Studio 3T, the IDE for MongoDB - https://studio3t.com/
);
Here are the models (points log below)
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var schema = new Schema({
orderNumber: {type: String, required: true},
productPurchased: {type: String, require: true},
answer: {type: String},
user: [{type: Schema.Types.ObjectId, ref: 'User'}]
});
module.exports = mongoose.model('PointsLog', schema);
Prizes model below
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var schema = new Schema({
title: {type: String, required: true},
content: {type: String, required: true},
orderNumber: {type: String, required: true},
imageUrl: {type: String, required: true},
stockQty: {type: Number, required: true},
question: {type: String, required: true},
answers: [{type: String, required: true}],
image: {type: String, required: true},
cost: {type: Number, required: true },
//entries: {type: Int},
//user: {type: Schema.Types.ObjectId, ref: 'User'}
user: [{type: Schema.Types.ObjectId, ref: 'User'}]
});
module.exports = mongoose.model('Prize', schema);
Not sure why lookup is returning nothing
1)You can use the upgraded lookup
Below is an Example->
db.pointslogs.aggregate([
// Stage 1
{
$match: {
"user": ObjectId("5aacff47c67f99103bcbf693")
}
},
// Stage 2
{
$group: {
_id: "$productPurchased",
orderNumber: { $push: "$orderNumber" }
}
},
// Stage 3
{
$unwind: {
path : "$orderNumber",
}
},
// Stage 4
{
$lookup:
{
from: "prizes",
let: { oNumber: "$orderNumber" },
pipeline: [
{ $match:{ $expr: { $eq: [ "$orderNumber", "$$oNumber" ] } }
}
},
{ $project: {
title: 1,
content: 1,
orderNumber: 1,
imageUrl: 1,
stockQty: 1,
question: 1,
answers: 1,
image: 1,
cost: 1,
user: 1
} }
],
as: "orderNumber"
}
}
])