const productSchema = mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
name: {
type: String,
required: true,
},
image: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
default: 0,
},
},
{
timestamps: true,
}
);
const Product = mongoose.model("Product", productSchema);
module.exports = Product;
Hello, Please is it possible to get list or count of users that referenced the product model from the product route or model? i know i get get it through the user route but can i do it from the product route? thanks
Product.aggregate([
{
"$lookup": {
from: "User", /// name of user collection
"localField": "user",
"foreignField": "_id",
"as": "user"
}
},
])
this will add user object as user in product doc as array, so you could have all data of user here and list of them
Related
I have an Order schema, like so:
const orderSchema = new mongoose.Schema({
order_items: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'OrderItem',
required: true
}],
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
}
total_price: {
type: Number
}
});
And the OrderItems contains purchased products, like so:
const orderItemSchema = new mongoose.Schema({
product_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true
},
quantity: {
type: Number,
required: true
}
});
And the Product schema like so:
const productSchema = new mongoose.Schema({
name: {
type: Map,
of: String,
required: true
},
thumbnail: {
type: String,
default: ''
},
unit_price: {
type: Number,
required: true
}
});
I'm trying to get the share of each purchased product from the total price of the order.
I tried the following:
const totalSales = await Order.aggregate([
{
$lookup: {
from: "orderitems",
localField: "order_items",
foreignField: "_id",
as: "order_items"
}
},
{
$lookup: {
from: "products",
localField: "order_items.product_id",
foreignField: "_id",
as: "products",
pipeline: []
}
},
{
$project: {
order_items: 0,
products: { $divide: ['$products.unit_price', '$total_price'] }
}
}
]);
But I got the following error in postman:
Invalid $project :: caused by :: Cannot use expression other than
$meta in exclusion projection
So, how can I get the desired output?
Thanks
Edit:
I removed order_items: 0 from the project, and now I got this error message:
PlanExecutor error during aggregation :: caused by :: $divide only
supports numeric types, not array and int
From the documentation,
If you specify the exclusion of a field other than _id, you cannot
employ any other $project specification forms.
You should remove order_items: 0, because it is anyway not included in the output.
I am creating express server for the project and I used the mongodb for the database. I am getting error,
Error validating $lookup value. err=$lookup against an expression value is not allowed in this atlas tier.
movies schema,
const mongoose = require("mongoose");
const MovieSchema = new mongoose.Schema(
{
title: { type: String, required: true, unique: true },
desc: { type: String },
img: { type: String },
imgTitle: { type: String },
imgSm: { type: String },
trailer: { type: String },
video: { type: String },
year: { type: String },
limit: { type: Number },
genre: { type: String },
isSeries: { type: Boolean, default: false },
},
{ timestamps: true }
);
module.exports = mongoose.model("movies", MovieSchema);
My lists schema,
const ListSchema = new mongoose.Schema(
{
title: { type: String, required: true, unique: true },
type: { type: String },
genre: { type: String },
content: [
{ type: mongoose.Schema.Types.ObjectId, required: false, ref: "movies" },
],
},
{ timestamps: true }
);
lists schema has a content property and it's contains the ids of the movies.so I need to have get all movies in the list with sample size 4,so I write aggregation for the list schema for populating movies object in content property in list schema. So where is the bug in my code ?I hope your help!
so I coded it as,
const list = await List.aggregate([
{ $sample: { size: 4 } },
{
$lookup: {
from: "$movies",
foreignField: "_id",
localField: "content",
as: "content",
},
},
]);
}
res.status(200).json(list);
I have an orders collection where each order has the following shape:
{
"_id": "5252875356f64d6d28000001",
"lineItems": [
{ productId: 'prod_007', quantity: 3 },
{ productId: 'prod_003', quantity: 2 }
]
// other fields omitted
}
I also have a products collection, where each product contains a unique productId field.
How can I populate each lineItem.productId with a matching product from the products collection? Thanks! :)
EDIT: orderSchema and productSchema:
const orderSchema = new Schema({
checkoutId: {
type: String,
required: true,
},
customerId: {
type: String,
required: true,
},
lineItems: {
type: [itemSubSchema],
required: true,
},
});
const itemSubSchema = new Schema(
{
productId: {
type: String,
required: true,
},
quantity: {
type: Number,
required: true,
},
},
{ _id: false }
);
const productSchema = new Schema({
productId: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
imageURL: {
type: String,
required: true,
},
price: {
type: Number,
default: 0,
},
});
I don't know the exact output you want but I think this is what you are looking for:
The trick here is to use $lookup in an aggregation stage.
First $unwind to deconstruct the array and can merge each id with the other collection.
Then the $lookup itself. This is like a join in SQL. It merges the desired objects with same ids.
Then recreate the population using $mergeObjects to get properties from both collections.
And last re-group objects to get the array again.
db.orders.aggregate([
{
"$unwind": "$lineItems"
},
{
"$lookup": {
"from": "products",
"localField": "lineItems.productId",
"foreignField": "_id",
"as": "result"
}
},
{
"$set": {
"lineItems": {
"$mergeObjects": [
"$lineItems",
{
"$first": "$result"
}
]
}
}
},
{
"$group": {
"_id": "$_id",
"lineItems": {
"$push": "$lineItems"
}
}
}
])
Example here
With this query you have the same intial data but "filled" with the values from the other collection.
Edit: You can also avoid one stage, maybe it is clear with the $set stage but this example do the same as it merge the objects in the $group stage while pushing to the array.
You can use the Mongoose populate method either when you query your documents or as middleware. However, Mongoose only allows normal population on the _id field.
const itemSubSchema = new Schema({
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'productSchema',
}
});
const order = await orderSchema.find().populate('lineItems.$*.product');
// special populate syntax necessary for nested documents
Using middleware you would still need to reconfigure your item schema to save the _id from products. But this method would automatically call populate each time you query items:
itemSubSchema.pre('find', function(){
this.populate('product');
});
You could also declare your item schema within your order schema to reduce one layer of joining data:
const orderSchema = new Schema({
lineItems: [{
type: {
quantity: {type: Number, required: true},
product: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'productSchema',
}
},
required: true,
}]
});
const orders = orderSchema.find().populate('lineItems');
I know this was asked before, but I can't find an answer that works for me.
I have some documents, which have reference to another document, like users and orders:
Users model:
import mongoose from '../database/index.js';
import mongoosePaginate from 'mongoose-paginate-v2';
const UsersSchema = new mongoose.Schema({
user_id: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
firstName: {
type: String,
default: '',
},
lastName: {
type: String,
default: '',
},
orders: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Orders',
},
],
recipients: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Recipients',
},
],
createdAt: {
type: Date,
default: Date.now,
required: true,
select: false,
},
});
UsersSchema.index({ email: 'text', firstName: 'text', lastName: 'text' });
UsersSchema.plugin(mongoosePaginate);
UsersSchema.set('toJSON', {
virtuals: true,
});
const Users = mongoose.model('Users', UsersSchema);
export default Users;
Orders model:
import mongoose from '../database/index.js';
import mongoosePaginate from 'mongoose-paginate-v2';
import Users from './users.js';
import OrderStatus from '../enums/OrderStatusEnum.js';
const OrdersSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Users',
required: true,
},
total: {
type: Number,
required: true,
},
status: {
type: String,
enum: OrderStatus.values(),
required: true,
default: OrderStatus.CREATED,
},
payment: {
type: mongoose.Schema.Types.ObjectId,
ref: 'PaymentMethods',
required: true,
},
shortId: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: Date.now,
required: true,
},
});
OrdersSchema.index({ shortId: 'text' });
OrdersSchema.plugin(mongoosePaginate);
OrdersSchema.pre('remove', function (next) {
Users.update({ orders: this._id }, { $pull: { orders: this._id } }).exec();
next();
});
OrdersSchema.set('toJSON', {
virtuals: true,
});
const Orders = mongoose.model('Orders', OrdersSchema);
export default Orders;
I can use the $text search to query full text search from orders or users:
const orders = await Orders.paginate(
{ $text: { $search: query.filter.q } },
query.options
);
return orders;
But this will only make a search on the orders index. If, i.e., I would like to search for the order of the user whose first name is Joe, how can I make it also search in the user.firstName field, since this is a reference from Orders to Users?
I know I can't populate the fields and then search on all model, but I'm not sure how to achieve what I'm looking for.
Thanks in advance
Because full text search query must be the first stage in the aggregation pipeline, it is not currently possible to perform full text search in two collections as part of the same query.
You'll need to reorganize your data.
my requirement was to find orders of users matched with their names, partial or full-text search. the user id is ref into orders doc.
from mongodb playground:
https://mongoplayground.net/p/ASOSFvfURXW
db.inventory.aggregate([
{
"$lookup": {
"from": "orders",
"localField": "orderId",
"foreignField": "_id",
"as": "order_docs"
}
},
{
"$unwind": "$order_docs"
},
{
"$match": {
"order_docs.item": {
"$regex": "pec",
"$options": "i"
}
}
}
])
I have two Documents:
Category = new Schema({
storeKey: { type: String, required: true },
cod: { type: String, required: true },
name: { type: String, required: true },
visible: { type: Boolean }
},
{
timestamps: {
createdAt: "created",
updatedAt: "updated"
}
}
);
and:
Product = new Schema({
name: { type: String, required: true },
cod: String,
storeKey: { type: String, required: true },
categoryId: String,
description: { type: String, required: true },
price: { type: Number, required: true },
stockQuantity: { type: Number, required: true },
avatar: String,
visible: Boolean
}, {
timestamps: true
});
Query on server whith mongoose to locate products with the aggregate category
Product.aggregate([
{
$lookup: {
from: "Category",
localField: "categoryId",
foreignField: "_id",
as: "category"
}
}]
).exec((error, done) => {
if (error) res.status(500).send({
message: langs[req.query.lang],
error
});
res.status(200).send(done);
});
query on local terminal
db.Product.aggregate(
[{
$lookup: {
localField: "categoryId",
from: "Category",
foreignField: "_id",
as: "category"
}
}])
In the terminal, $lookup works correctly. With mongoose, it brings duplicate records and does not bring existing categories. What is wrong?
#Anthony Winzlet was correct, should be categories (in the pural) would have to leave categories (in the pural) and not category, but also, i had not defined the categoryId field as being an ObjectId in the Product Schema, so it was comparing string with ObjectId. In the tests in the terminal I had saved the server return, which returns the _id fields as strings. Now it's working.Thanks!
const ProductSchema = new Schema({
name: { type: String, required: true },
cod: String,
storeKey: { type: String, required: true },
categoryId: { type: Schema.Types.ObjectId, ref: 'categories' },
description: { type: String, required: true },
price: { type: Number, required: true },
stockQuantity: { type: Number, required: true },
avatar: String,
visible: Boolean
}, {
timestamps: true
});