Mongoose - How to get unique data based on some fields using aggregation - node.js

I have these fields in the document,
doc: {
"id": "632ac8cba7723378033fef10",
"question": 1,
"text": "aasdfghjk,mnbvcxswertyuikmnbvcxsrtyuiknbvcdrtyujnbvcddtyjnbvfty",
"slug": "xcvbnrddfghjktdxjjydcvbyrsxcvbhytrsxggvbjkytrdgc",
"subject": 25866,
"tutorInfo": {
"tutorId": "632ac8cba7723378033fa0fe",
"tutorIncrementalId": 95947
}
}
the same tutorInfo can Occur in multiple documents.
const allQuestionBySubject = await QuestionParts.aggregate([
{
$match: {
$and: [
{
subject: subjectIncrementalId
},
{tutorInfo: {$exists: true}}
]
}
},
{ "$skip": page * limit },
{ "$limit": limit },
{
$lookup: {
from: "profiles",
localField: "tutorInfo.tutorIncrementalId",
foreignField: "incrementalId",
as: "tutorDetails"
}
}
])
Code to get a list of questions as per subject.
I am filtering documents based on subject and as I mentioned the same tutorInfo can be present in multiple documents so in the result same tutor can be present in multiple documents, How can I get a unique list of documents in which tutorInfo shouldn't be repeated.

Since the same tutorInfo is present in multiple records, You can use $group to group the document on the tutorInfo.tutorId field.
const allQuestionBySubject = await QuestionParts.aggregate(
[
{
$match: {
$and: [
{
subject: subjectIncrementalId
},
{ tutorInfo: { $exists: true } }
]
}
},
{ "$skip": page * limit },
{ "$limit": limit },
{
"$group": {
_id: "$tutorInfo.tutorId",
question: { $first: "$question" },
text: { $first: "$text" },
slug: { $first: "$slug" },
subject: { $first: "$orderId" },
tutorInfo: { $first: "$tutorInfo" },
}
},
{
$lookup: {
from: "profiles",
localField: "tutorInfo.tutorIncrementalId",
foreignField: "incrementalId",
as: "tutorDetails"
}
}
]
)

Related

Show documents from Array with MongoDB

I would like to "create" new documents, to keep only the contents of the activities.
I've looked at $project and $unwide, but I didn't find any appropriate result.
Here is my MongoDB query:
const activities = await friendModel.aggregate([
{
$match: {
$or: [
{
ReceiverId: ActorId,
Status: 1,
},
{
RequesterId: ActorId,
Status: 1,
},
],
},
},
{
$set: {
fieldResult: {
$cond: {
if: {
$eq: [ "$ReceiverId", ActorId ],
},
then: "$RequesterId",
else: "$ReceiverId",
},
},
},
},
{
$lookup: {
from: "activities",
localField: "fieldResult",
foreignField: "ActorId",
as: "activity",
},
},
{
$unwind: {
path: "$activity",
preserveNullAndEmptyArrays: true
}
},
{ $sort: { "activity._Date": -1 } },
{ $skip: request.pageindex * 3 },
{ $limit: 3 },
]);
Here is what I would like to do: https://sourceb.in/7u0tirIstt
Here is what I get for the moment: https://sourceb.in/Ped2xY5Ml8
Thank you in advance for your help!
You should be able to add $project as follows:
...
{
$project: {
"_id":"$_id",
"ActivityId": "$activity._id",
"ActorId": "$activity.ActorId",
"Type": "$activity.Type",
"_Date": "$activity._Date",
"MovieId": "$activity.MovieId",
"FriendId": "$activity.FriendId",
"ContestId": "$activity.ContestId",
"LookId": "$activity.LookId",
"__v": "$__v"
},
},
...
You can access values with $, and map nested values by accessing them directly
Also would recommend you doing a limit before you do lookup to speed it up a bit

retrieve nested data from mongodb record depend on condition

I have a board record like this
{
_id:"6257d2edfbc4d4d1d53bb7b8"
tasks:["624acd4fce5fbb5ce20ddb88","624acd68ce5fbb5ce20de3b5"]
}
This is my tasks collection
[
{
_id:"624acd4fce5fbb5ce20ddb88",
status:"inProgress"
},
{
_id:"624acd68ce5fbb5ce20de3b5"
status:"done"
}
]
I am trying to retrieve the board data and the count of inProgress tasks and done tasks in aggregate like this but it show me this error
input to $filter must be an array not object
Board.aggregate([
{
$facet: {
totalDocs: [{ $match: { $and: [data] } }],
totalInProgress: [
{
$lookup: {
from: "tasks",
localField: "tasks",
foreignField: "_id",
as: "tasks",
},
},
{ $unwind: { path: "$tasks", preserveNullAndEmptyArrays: true } },
{
$project: {
tasks: {
$filter: {
input: "$tasks",
as: "task",
cond: { $eq: ["$$task.status", "inProgress"] },
},
},
},
},
],
},
},
]);

Mongoose join two collections and get referenced data in two properties

I'm doing a simple follow friend functionality.
Please see my codes below:
Following schema:
{
"userId": { type: String },
"followers": [{ "followerId": type: String }],
"followings": [{ "followingId": type: String }],
}
User schema:
{
"fullName": { type: String }
}
Note: user 8 has 1 follower and 2 followings.
Now, my expected output should be like this:
"userId": 8,
"followers": [
{
"followerId": 4,
"fullName": "Rose Marriott",
},
{
"followerId": 5,
"fullName": "James Naismith",
}
],
"followings": [
{
"followingId": 1,
"fullName": "Russell Oakham",
},
{
"followingId": 5,
"fullName": "James Naismith",
}
]
This is what I tried so far:
db.followings.aggregate([
{ $unwind: "$followers" },
{
$lookup: {
from: "users",
localField: "followers.followerId",
foreignField: "_id",
as: "users"
}
},
{
$addFields:
{
users: { $arrayElemAt: ["$users", 0] },
},
},
{ $unwind: "$followings" },
{
$lookup: {
from: "users",
localField: "followings.followingId",
foreignField: "_id",
as: "users2"
}
},
{
$addFields:
{
users2: { $arrayElemAt: ["$users2", 0] },
},
},
{ $match: {"userId": mongoose.Types.ObjectId(userId) } },
{
$group: {
_id: "$_id",
userId: { $first: "$userId" },
followers: {
$push: {
followerId: "$followers.followerId",
fullName: "$users.fullName",
}
},
followings: {
$push: {
followingId: "$followings.followingId",
fullName: "$users2.fullName",
}
}
}
}
]);
But I'm getting 2 followers and 2 followings. I wonder what's causing this issue. Appreciate any help. Thanks!
You can try,
$addFields to make a unique array called userIds form both arrays followers and followings, $setUnion to get unique ids,
$lookup with users collection
$project to show fields,
followers get fullName, $map to iterate loop of followers and get the name of followerId from users array using $reduce and $cond
followings get fullName, $map to iterate loop of followings and get the name of followingId from users array using $reduce and $cond
db.followings.aggregate([
{
$addFields: {
userIds: {
$setUnion: [
{
$map: {
input: "$followers",
in: "$$this.followerId"
}
},
{
$map: {
input: "$followings",
in: "$$this.followingId"
}
}
]
}
}
},
{
$lookup: {
from: "users",
localField: "userIds",
foreignField: "_id",
as: "users"
}
},
{
$project: {
userId: 1,
followers: {
$map: {
input: "$followers",
as: "f",
in: {
$mergeObjects: [
"$$f",
{
fullName: {
$reduce: {
input: "$users",
initialValue: "",
in: {
$cond: [
{ $eq: ["$$this._id", "$$f.followerId"] },
"$$this.fullName",
"$$value"
]
}
}
}
}
]
}
}
},
followings: {
$map: {
input: "$followings",
as: "f",
in: {
$mergeObjects: [
"$$f",
{
fullName: {
$reduce: {
input: "$users",
initialValue: "",
in: {
$cond: [
{ $eq: ["$$this._id", "$$f.followingId"] },
"$$this.fullName",
"$$value"
]
}
}
}
}
]
}
}
}
}
}
])
Playground
when working with relations on Mongoose you should create the relationship based on a unique id, and then reference the document. In your case, it would be:
followingSchema = new mongoose.Schema({
{
"followers": [{type: mongoose.Schema.types.ObjectId, ref="User"}],
"followings": [{type: mongoose.Schema.types.ObjectId, ref="User"}],
}
})
userSchema = new mongoose.Schema({
fullname: String,
})
be aware that the userId will be created automatically by Mongoose with a field called _id. so, the end result of creating a new following documents would be:
{
_id: "klajdsfñalkdjf" //random characters created by mongoose,
followers: ["adfadf134234", "adfadte345"] //same as before, these are Ids of the users randomly generated by mongoose
followers: ["adfadf134234", "adfadte345"]
}
{
_id: adfadf134234,
fullName: "alex",
}
now, because there is no use for us to have a random number as information in the fields of following and followers in the following object, we can now use the method .populate() that can be used over the document itself to transform those Ids into actual information. You can see more about it here: mongoose documentation
our final result would be something like this:
{
_id: "añfakdlsfja",
followers : [{_id: "adlfadsfj", fullName: "alex"}],
following : [{_id: "adfadfad" , fullName: "other"}, {_id: "dagadga", fullName: "another"}]
}

Populate one collection with lookup in mongoose

I'm trying to populate a collection with lookup. The thing is I need to join three collections at once using mongoose in express js.
I have three collections namely, users, skills, userskills.
User and UserSkills are connected. Skills and UserSkills are connected. But not User and Skills.
My model is like
users
{
_id: 5ec6d940b98e8f2c3cea5f22,
name: "test one",
email: "test#example.com"
}
skills
{
_id: 5ec786b21cea7d8c8c186a54,
title: "java"
}
user-skills
{
_id: 5ec7879c1cea7d8c8c186a56,
skillId: "5ec786b21cea7d8c8c186a54",
userId: "5ec6d940b98e8f2c3cea5f22",
level: "good"
}
I tried
user = await User.aggregate([{
$lookup: {
from: "userskills",
localField: "_id",
foreignField: "userId",
as: "userskills"
}
}, {
$unwind: {
path: "$userskills",
preserveNullAndEmptyArrays: true
}
}, {
$lookup: {
from: "skills",
localField: "userskills.skillId",
foreignField: "_id",
as: "userskills.skill",
}
}, {
$group: {
_id : "$_id",
name: { $first: "$name" },
skill: { $push: "$skills" }
}
}, {
$project: {
_id: 1,
name: 1,
skills: {
$filter: { input: "$skills", as: "a", cond: { $ifNull: ["$$a._id", false] } }
}
}
}]);
Required result:
{
"users" : [
{
_id: "5ec6d940b98e8f2c3cea5f22"
name: "test one",
email: "testone#example.com",
"skills" : [
{
_id: "5ec7879c1cea7d8c8c186a56",
level: "good",
"skill" : {
_id: "5ec786b21cea7d8c8c186a54",
title: "java"
}
}
]
},
{
_id: "5ec6d940b98e8f2c3cea5f23"
name: "test two",
email: "testtwo#example.com",
"skills" : [
{
_id: "5ec7879c1cea7d8c8c186a57",
level: "good",
"skill" : {
_id: "5ec786b21cea7d8c8c186a55",
title: "php"
}
}
]
}
]
}
when using
user = await User.find().populate('Skills").exec()
The result is coming like
{
"users" : [
{
_id: "5ec6d940b98e8f2c3cea5f22"
name: "test one",
email: "testone#example.com",
"skills" : [
{
_id: "5ec7879c1cea7d8c8c186a56",
level: "good",
skillId: "5ec786b21cea7d8c8c186a55"
}
]
}
]
}
The problem is I need the skill name should also be fetched. Please help me to solve this issue. I'm writing a backend API in nodejs and mongodb.
You can embed that second $lookup as part of custom pipeline:
await User.aggregate([
{
$lookup: {
from: "userskills",
let: { user_id: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: [ "$$user_id", "$userId" ] } } },
{
$lookup: {
from: "skills",
localField: "skillId",
foreignField: "_id",
as: "skill"
}
},
{ $unwind: "$skill" }
],
as: "skills"
}
}
])
Mongo Playground

Best a very overloaded query mongodb - Nodejs; aggregate, group, porject and lookup

its my first question in this forum. (I am a Spanish speaker, sorry for my basic English)
I have 3 collections: images, artists and exhibitions.
Images has: filename (unique), path and _id and etc.
Artists has: name, url (unique) and description and etc
Exhibitions has: name, url (unique), year, abstract, aristUrl (artist.url) imageCover (image.fieldname) and etc.
Whit my query this is my summary result:
[
_id: null,
documents: [{...}, {...}],
totalExhibitions: 2
]
Full result
[
{
"_id": null,
"documents": [
{
"_id": "5e84d6599891212db0a6dc7e",
"url": "chile",
"imageCover": [
{
"path": "http://localhost:2616/uploads/images/1585763637647.jpg"
}
],
"name": "Almacén Verdad y Justicia",
"year": 2010,
"releaseDate": "2010-08-30T00:00:00.000Z",
"artist": [
{
"name": "Bernardo de Castro Saavedra",
"url": "bernardo-castro-saavedra"
}
]
},
{
"_id": "5e84e0575a3f201aac2df1c2",
"url": "sin-cera",
"imageCover": [
{
"path": "http://localhost:2616/uploads/images/1585766437587.jpg"
}
],
"name": "Sin cera",
"year": 2020,
"releaseDate": "2020-01-31T00:00:00.000Z",
"artist": [
{
"name": "Gonzalo Tapia",
"url": "gonzalo-tapia"
}
]
}
],
"totalExhibitions": 2
}
]
This is my code
getByLastYear(){
const documents = this.mongoDB.aggregate([
{
$addFields: {
"artistObjectId": { $toObjectId: "$artistId" }
}
},{
$lookup: {
from: 'artists',
localField: 'artistObjectId',
foreignField: "_id",
as: "artist"
},
},{
$lookup: {
from: 'images',
localField: 'imageCover',
foreignField: "filename",
as: "imageCover"
},
},{
$project: {
name: 1,
year: 1,
url: 1,
releaseDate: 1,
artist: {
name: 1,
url: 1
},
imageCover: {
path: 1,
alt: 1,
}
}
}, {
$group: {
_id: null,
documents: {
$push: "$$ROOT"
},
totalExhibitions: {
$sum: 1
}
}
}
]);
return documents || [];
};
that is the best form to get my result?
is there any better?
Thank you for your comments and opinions.<3
Assuming that:
you're starting from the exhibitions collection;
the "_id" fields have an ObjectId format;
there's only 1 artist and 1 cover image for each exhibition...
I'd skip the first step and add in $unwind stages to have the artist and the cover image as a subdocument instead of an array.
The reason for that is that it will make it easier to reference the result.
This should be able to run in your Mongo shell:
db.exhibitions.aggregate([
{ $lookup: {
from: 'artists',
localField: "artistId",
foreignField: "_id",
as: "artist"
}},
{ $unwind: {
path: "$artist",
preserveNullAndEmptyArrays: true
}},
{ $lookup: {
from: 'images',
localField: 'imageCover',
foreignField: "filename",
as: "imageCover"
}},
{ $unwind: {
path: "$imageCover",
preserveNullAndEmptyArrays: true
}},
{ $project: {
name: 1,
year: 1,
url: 1,
releaseDate: 1,
artist: {
name: "$artist.name",
url: "$artist.url"
},
imageCover: {
path: "$imageCover.path",
alt: "$imageCover.alt",
}
}},
{ $group: {
_id: null,
documents: {
$push: "$$ROOT"
},
totalExhibitions: {
$sum: 1
}
}}
]);
In the "$project" stage, for artist and imageCover, you would need to specify the full path to build the reduced subdocument.
Hope that answered your question...

Resources