I have 3 collections, User, Dispensary and City. I want my result to look like this:
{
_id: ,
email: ,
birthdate: ,
type: ,
dispensary: {
_id: ,
schedule: ,
name: ,
address: ,
phone: ,
user:,
city: {
name:,
},
},
}
However I am getting the city object out, in the first level, and I want to get it as child of the dispensary collection.
This is my current pipeline I'm using:
User.aggregate
([
{
$match: { "_id": id }
},
{
$lookup:
{
from: Dispensary.collection.name,
localField: "dispensary",
foreignField: "_id",
as: "dispensary"
},
},
{"$unwind": {path:"$dispensary",preserveNullAndEmptyArrays: true} ,},
{
$lookup:
{
from: City.collection.name,
localField: "dispensary.city",
foreignField: "_id",
as: "city"
},
},
{"$unwind": {path:"$city",preserveNullAndEmptyArrays: true}} ,
{
"$group": {
_id: "$_id",
email : { $first: '$email' },
birthdate : { $first: '$birthdate' },
type : { $first: '$type' },
dispensary: { $push: "$dispensary" },
city: { $push: "$city" },
},
},
{"$unwind": {path:"$dispensary",preserveNullAndEmptyArrays: true}} ,
{"$unwind": {path:"$city",preserveNullAndEmptyArrays: true}} ,
], (aggErr, aggResult) => {
(aggErr) ? console.log(aggResult)
: console.log(aggResult)
})
SCHEMAS:
const CitySchema = new Schema({
name: { type: String, required: true, unique:true },
zip: { type: String, required: true },
});
const DispensarySchema = new Schema({
name: { type: String, required: true },
address: { type: String, required: true },
longitude: { type: String, required: true },
latitude: { type: String, required: true },
phone: { type: String, required: true },
user: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
schedule: [{type: mongoose.Schema.Types.ObjectId, ref: 'Schedule'}],
city: {type: mongoose.Schema.Types.ObjectId, ref: 'City'},
})
const UserSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type:String, required: true },
birthdate: { type: Date, required: true },
type: { type: String, enum: ['ADMIN','DISPENSARY','CUSTOMER'], required: true},
verificationToken: { type: String, required: false },
resetPasswordToken: { type: String, required: false },
resetPasswordExpires: { type: String, required: false },
isVerified: { type: Boolean, required: true },
isActive: { type: Boolean, required: true },
last_session: { type: Date },
last_ip_session: { type:String },
dispensary: {type: mongoose.Schema.Types.ObjectId, ref: 'Dispensary'},
},
{ timestamps: true }
)
I think you don't need to use $group at all, you can use as: "dispensary.city" in your second $lookup
[
{
"$match": {
"_id": id
}
},
{
"$lookup": {
from: Dispensary.collection.name,
localField: "dispensary",
foreignField: "_id",
as: "dispensary"
},
},
{
"$unwind": {
path: "$dispensary",
preserveNullAndEmptyArrays: true
},
},
{
"$lookup": {
from: City.collection.name,
localField: "dispensary.city",
foreignField: "_id",
as: "dispensary.city" // modify here
},
},
{
"$unwind": {
path: "$dispensary.city", // modify here
preserveNullAndEmptyArrays: true
}
}
]
You can used another lookup method using pipeline, This allow you to make more condition/sub-query inside of the lookup function. see reference: aggregate-lookup
User.aggregate([
{
$match: { "_id": id }
},
{
$lookup: {
from: Dispensary.collection.name,
let: {dispensaryId: "$dispensary"},
pipeline: [
{
$match: {
$expr: {
$eq: ["$_id", "$$dispensaryId"]
}
}
},
{
$lookup:
{
from: City.collection.name,
localField: "city",
foreignField: "_id",
as: "city"
},
},
{
$unwind: {
path:"$city",
preserveNullAndEmptyArrays: true
}
}
]
as: "dispensary",
},
},
{
$unwind: {
path:"$dispensary",
preserveNullAndEmptyArrays: true
}
},
{
"$group": {
_id: : {
_id: "$_id",
email : '$email' ,
birthdate : '$birthdate' ,
type : '$type'
dispensary: "$dispensary"
}
}
}
], (aggErr, aggResult) => {
(aggErr) ? console.log(aggResult)
: console.log(aggResult)
})
Update: Pipeline NOTE: To reference variables in pipeline stages, use the "$$" syntax.`
Related
i wanna show categories name in that view who users can see it beside product
actually in single page view of product but i think i have some problems in relationships of mongo because it just return an empty array!
i will appreciate if you help me
product model :
const createProductSchema = Schema(
{
user: { type: Schema.Types.ObjectId, ref: "User" },
categories: [{ type: Schema.Types.ObjectId, ref: "Category" }],
title: { type: String, required: true },
type: { type: String, required: true },
slug: { type: String, required: true },
body: { type: String, required: true },
images: { type: Object, required: true },
thumb: { type: String, required: true },
price: { type: String, required: true },
tags: { type: String, required: true },
viewCount: { type: Number, default: 0 },
},
{ timestamps: true, toJSON: { virtuals: true } }
);
createProductSchema.virtual("category", {
ref: "Category",
localField: "_id",
foreignField: "products",
});
category model :
const categorySchema = Schema(
{
products: [{type: Schema.Types.ObjectId, ref: "Product" }],
name: { type: String, required: true },
slug: { type: String, required: true },
parent: { type: Schema.Types.ObjectId, ref: "Category", default: null },
},
{ timestamps: true, toJSON: { virtuals: true } }
);
productController:
let product = await Product.findOne({ slug: req.params.product })
.populate([
{
path: "user",
select: "name",
},
{
path:"category",
select:"name",
}
]);
res.json(product);
and in output i just can see category:[]
:((((((
You should populate categories, not category:
let product = await Product.findOne({ slug: req.params.product })
.populate([
{
path: 'user',
select: 'name',
},
{
path: 'categories',
select: 'name',
},
])
.exec();
res.json(product);
I have a list of task records (see schema below). I am attempting to return records for a specific projectId, group my task records by status, and populate the responsible field. However, the responsible field is not populating. I have attached a code snippet below. Can anyone advise what I am doing incorrectly?
Code Snippet:
const test = await Task.aggregate([
{
$match: { project: { $eq: mongoose.Types.ObjectId(projectId) } },
},
{
$group: {
_id: "$status",
data: {
$push: {
_id: "$status",
name: "$name",
responsible: "$responsible",
endDate: "$endDate",
},
},
},
},
{
$sort: { status: 1 },
},
]);
console.log("test1:", test);
// Populate Aggregated Data:
const tasks = await User.populate(test, { path: "data.responsible" });
console.log("test2:", tasks);
TaskSchema:
const TaskSchema = new mongoose.Schema({
name: {
type: String,
trim: true,
required: true,
},
responsible: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
endDate: {
type: Date,
required: false,
},
status: {
type: String,
enum: ["new", "active", "inactive", "closed"],
required: true,
},
project: {
type: mongoose.Schema.Types.ObjectId,
ref: "Project",
},
});
Desired Results:
results = [
{
_id: “new”
data: [
{
endDate: "2022-09-16T04:00:00.000Z”,
name: "test1”,
responsible: {
email: “jane.doe#ymail.com”,
firstName: “Jane”,
lastName: “Doe”
},
{
endDate: "2022-09-16T04:00:00.000Z”,
name: "test2”,
responsible: {
email: “john.doe#ymail.com”,
firstName: “John”,
lastName: “Doe”
},
]
},
]
I'm trying to get some datas by mongoose.
let datas =
await Data.find({
'$and': [
{ test_id: testId },
{ createdAt: { $gte: moment(req.query.from).startOf('day') } },
{ createdAt: { $lte: moment(req.query.to).endOf('day') } }
],
});
This is works. But, I try to change aggregate, I can't get any data.
How should I get same result?
const datas = await Data.aggregate([
{
'$match': {
'$and': [
{ test_id: testId },
{ createdAt: { $gte: moment(req.query.from).startOf('day') } },
{ createdAt: { $lte: moment(req.query.to).endOf('day') } }
],
}
}]);
My definition is like this.
const Data = new Schema({
test_id: {
type: String,
required: true,
},
user_id: {
type: String,
required: true,
},
item: {
agent_id: { type: String, ref: 'Company' },
product_no: { type: String, required: true },
user_id: { type: String, required: true, ref: 'User' },
name: { type: String, required: true },
quantity: { type: Number, required: true },1 },
standard_price: { type: Number},
designation: { type: String },
category: { type: String },
weight: { type: Number },
materials: { type: String },
remarks: { type: String },
note: { type: String },
location: String,
image: String
}
}, {
timestamps: true,
});
And when I change the condition only { test_id: testId },
I can get the data. I doubt of term conditions in the aggregate.
But I don't know how to resolve it.
mongoose version is "5.11.15".
"mongoose": "5.11.15",
I'm trying to do an inner join that matches, but for some reason I get a left join.
I have 2 relations tables, and I want to get the movie with the genre names.
Consider the following models:
// Movie
const MovieSchema = new mongoose.Schema({
id: {
type: Number,
default: null,
required: true,
unique: true
},
title: {
type: String,
default: null,
required: true,
unique: true,
trim: true
},
});
const Movie = mongoose.model('Movie', MovieSchema);
module.exports = Movie;
// Genre
const GenreSchema = new mongoose.Schema({
id: {
type: Number,
default: null,
required: true,
unique: true
},
name: {
type: String,
default: null,
required: false,
trim: true,
unique: true
}
});
const Genre = mongoose.model('Genre', GenreSchema);
module.exports = Genre;
// MovieGenre
const MovieGenreSchema = new mongoose.Schema({
genreId: {
type: Number,
default: null,
required: true
},
movieId: {
type: Number,
default: null,
required: true
}
});
const MovieGenre = mongoose.model('MovieGenre', MovieGenreSchema);
module.exports = MovieGenre;
I try to do the following query:
{
$lookup:
{
from: MovieGenre.collection.name,
localField: 'id',
foreignField: 'movieId',
as: 'movieGenres'
}
},
{
$lookup: {
from: Genre.collection.name,
localField: 'g.id',
foreignField: 'genreId',
as: 'genreNames'
}
},
{
$match: {
'genreNames.name': 'Action'
}
}
and I get the results:
{
_id: 5ee9b51609f44c0f38262c94,
id: 26583,
title: 'The Keeper',
__v: 0,
movieGenres: [
{
_id: 5ee8b8cf0d186c20b4bf3ccd,
genreId: 28,
movieId: 26583,
__v: 0
},
{
_id: 5ee8b8cf0d186c20b4bf3cce,
genreId: 53,
movieId: 26583,
__v: 0
}
],
genreNames: [
{ _id: 5ee8b68f0d186c20b4b03a3d, id: 35, name: 'Comedy', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a3e, id: 80, name: 'Crime', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a40, id: 18, name: 'Drama', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a42, id: 53, name: 'Thriller', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a43, id: 28, name: 'Action', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a45, id: 14, name: 'Fantasy', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a46, id: 27, name: 'Horror', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a4a, id: 10752, name: 'War', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a4b, id: 10402, name: 'Music', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a4c, id: 37, name: 'Western', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a4d, id: 36, name: 'History', __v: 0 }
]
}
but what i expected to get is:
{
_id: 5ee9b51609f44c0f38262c94,
id: 26583,
title: 'The Keeper',
__v: 0,
movieGenres: [
{
_id: 5ee8b8cf0d186c20b4bf3ccd,
genreId: 28,
movieId: 26583,
__v: 0
},
{
_id: 5ee8b8cf0d186c20b4bf3cce,
genreId: 53,
movieId: 26583,
__v: 0
}
],
genreNames: [
{ _id: 5ee8b68f0d186c20b4b03a43, id: 28, name: 'Action', __v: 0 },
{ _id: 5ee8b68f0d186c20b4b03a42, id: 53, name: 'Thriller', __v: 0 },
]
}
Can you please tell me,
What am I doing wrong?
Thanks.
You just need to correct your second lookup with genre collection, i have added 2 approaches you can use anyone,
1) Using your approach:
localField pass previous lookup result's movieGenres.genreId
foreignField pass id of genre collection
{
$lookup: {
from: Genre.collection.name,
localField: "movieGenres.genreId",
foreignField: "id",
as: "genreNames"
}
}
if you want to filter the genreNames names from above lookup by name,
$filter to iterate loop of genreNames array and filter by name: Action
{
$addFields: {
genreNames: {
$filter: {
input: "$genreNames",
cond: { $eq: ["$$this.name", "Action"] }
}
}
}
}
Your final query would be,
{
$lookup: {
from: MovieGenre.collection.name,
localField: "id",
foreignField: "movieId",
as: "movieGenres"
}
},
{
$lookup: {
from: Genre.collection.name,
localField: "movieGenres.genreId",
foreignField: "id",
as: "genreNames"
}
},
{
$match: {
"genreNames.name": "Action"
}
},
{
$addFields: {
genreNames: {
$filter: {
input: "$genreNames",
cond: { $eq: ["$$this.name", "Action"] }
}
}
}
}
Playground
2) Using lookup with pipeline approach:
The alternate way to do this using lookup with pipeline,
let to pass movieGenres.genreId from above lookup
$match to match genreId using $expr expression match and name field and combine conditions using $and operations
{
$lookup: {
from: MovieGenre.collection.name,
localField: "id",
foreignField: "movieId",
as: "movieGenres"
}
},
{
$lookup: {
from: Genre.collection.name,
let: { genreIds: "$movieGenres.genreId" },
pipeline: [
{
$match: {
$and: [
{ $expr: { $in: ["$id", "$$genreIds"] } },
{ name: "Action" }
]
}
}
],
as: "genreNames"
}
}
Playground
I have two models user.js and schedule.js and I have a query (aggregate) that i need to use $lookup to "join" these models. After the $lookup i'm using a $project that select the fields that i want to show in the result of my query, but the fields scheduleStart and scheduleEnd don't show in my result.
User.js (model)
name: {
type: String,
required: true
},
firstName: {
String
},
lastName: {
String
},
storeKey: {
type: String,
required: true
},
avatar: String,
birthday: String,
phone: {
type: String
},
doc: String,
email: {
type: String
},...
Schedule.js (model)
service: {
id: {
type: String
},
name: {
type: String,
required: true
},
filters: [String]
},
info: {
channel: {
type: String,
required: true,
default: 'app'
},
id: String,
name: String
},
scheduleDate: {
type: String,
required: true
},
scheduleStart: {
type: String,
required: true
},
scheduleEnd: {
type: String,
required: true
},
My query
User.aggregate([{
$match: {
storeKey: req.body.store,
}
},
{
$group: {
_id: {
id: "$_id",
name: "$name",
cpf: "$cpf",
phone: "$phone",
email: "$email",
birthday: "$birthday",
lastName: "$lastname"
},
totalServices: {
$sum: "$services"
},
}
},
{
$lookup: {
from: "schedules",
localField: "_id.phone",
foreignField: "customer.phone",
as: "user_detail"
}
},
{
$project: {
_id: 1,
name: 1,
name: 1,
cpf: 1,
phone: 1,
email: 1,
birthday: 1,
totalServices: 1,
totalValue: { "$sum": "$user_detail.value" },
scheduleStart: 1,
scheduleEnd: 1,
count: {
$sum: 1
}
}
}
])...
Result of my query:
count: 1
totalServices: 89
totalValue: 2374
_id:{
birthday: "1964-03-18",
cpf: "319335828",
email: "jdoe#gmail.com.br",
id: "5b1b1dcce1ab2a8eb580f",
name: "Jonh Doe",
phone: "11996370565"
}
You can use below $project stage with $arrayElemAt aggregation
{ '$project': {
'_id': 1,
'name': 1,
'cpf': 1,
'phone': 1,
'email': 1,
'birthday': 1,
'totalServices': 1,
'totalValue': { '$sum': '$user_detail.value' },
'scheduleStart': { '$arrayElemAt': ['$user_detail.scheduleStart', 0] },
'scheduleEnd': { '$arrayElemAt': ['$user_detail.scheduleEnd', 0] },
'count': { '$sum': 1 }
}}