retrieve nested data from mongodb record depend on condition - node.js

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"] },
},
},
},
},
],
},
},
]);

Related

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

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"
}
}
]
)

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

Convert from localField to let - $lookup

I'm trying to adapt my code so I could after use the pipeline to $project only the fields I need.
This first code is achieving the result expected;
Order.aggregate([
{ $unwind: "$candidate" },
{ $match: { "candidate.groupId": "A" } },
{
$lookup: {
from: "users",
localField: "candidate.autonomousId",
foreignField: "_id",
as: "candidateData",
},
},
]).toArray();
The second one doesn't return the same output. It returns an empty candidateData.
Order.aggregate([
{ $unwind: "$candidate" },
{ $match: { "candidate.groupId": "A" } },
{
$lookup: {
from: "users",
let: { id: "$candidate.autonomousId" },
pipeline: [{ $match: { $expr: { $eq: ["$_id", "$$id"] } } }],
as: "candidateData",
},
},
]).toArray();
What am I doing wrong when I try to write in the second way? I couldn't find my syntax mistake.
Since candidate.autonomousId is an array (contrary to its name) and you are using the $eq operator to compare $$id (array) with _id (ObjectId), the pipeline returns no User documents in candidateData. You should use the $in aggregation operator instead.
db.orders.aggregate([
{
$unwind: "$candidate"
},
{
$match: {
"candidate.groupId": "A"
}
},
{
$lookup: {
from: "users",
let: {
id: "$candidate.autonomousId"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$_id",
"$$id"
]
}
}
}
],
as: "candidateData",
},
},
])
MongoPlayground
I would recommend renaming the let variable ids for clarity.

How to lookup inside lookup in MongoDB Aggregate?

I have a simple 3 collections. This bellow is their pseudocode. I want to get all shipments and for each shipment, I want to have all bids for that shipment and in each bid, I need userDetails object.
User: {
name: string,
}
Shipment: {
from: string,
to: string
}
Bid: {
amount: number,
shipmentId: Ref_to_Shipment
userId: Ref_to_User
}
This is what I have tried:
const shipments = await ShipmentModel.aggregate([
{
$lookup: {
from: "bids",
localField: "_id",
foreignField: "shipmentId",
as: "bids"
}
},
{
$lookup: {
from: "users",
localField: "bids.userId",
foreignField: "_id",
as: "bids.user"
}
}
])
And I got the following result:
[
{
"_id": "5fad2fc04458ac156531d1b1",
"from": "Belgrade",
"to": "London",
"__v": 0,
"bids": {
"user": [
{
"_id": "5fad2cdb4d19c80d1b6abcb7",
"name": "Amel",
"email": "Muminovic",
"password": "d2d2d2",
"__v": 0
}
]
}
}
]
I am trying to get all Shipments with their bids and users within bids. Data should look like:
[
{
"_id": "5fad2fc04458ac156531d1b1",
"from": "Belgrade",
"to": "London",
"__v": 0,
"bids": [
{
"_id": "5fad341887c2ae1feff73402",
"amount": 400,
"userId": "5fad2cdb4d19c80d1b6abcb7",
"shipmentId": "5fad2fc04458ac156531d1b1",
"user": {
"name": "Amel",
}
"__v": 0
}
]
}
]
Try with the following code:
const shipments = await ShipmentModel.aggregate([
{
$lookup: {
from: "bids",
localField: "_id",
foreignField: "shipmentId",
as: "bids"
}
},
{
$unwind: {
path: "$bids",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: "users",
localField: "bids.userId",
foreignField: "_id",
as: "bids.user"
}
}
])
If you want to prevent null and empty arrays then set
preserveNullAndEmptyArrays: false
Try this query and chek if works and the behaviour is as you expected:
db.Shipment.aggregate([
{
$lookup: {
from: "Bid",
localField: "id",
foreignField: "shipmentId",
as: "bids"
}
},
{
$lookup: {
from: "user",
localField: "id",
foreignField: "id",
as: "newBids"
}
},
{
$project: {
"newBids.id": 0,
"newBids._id": 0,
}
},
{
$match: {
"bids.userId": 1
}
},
{
$addFields: {
"newBids": {
"$arrayElemAt": [
"$newBids",
0
]
}
}
},
{
$set: {
"bids.user": "$newBids"
}
},
{
$project: {
"newBids": 0
}
}
])
This query do your double $lookup and then a $project to delete the fields you don't want, and look for the userId to add the field user. As $lookup generate an array, is necessary use arrayElemAt to get the first position.
Then $set this value generated into the object as bids.user and remove the old value.
Note that I have used a new field id instead of _id to read easier the data.
Try this
I figured out it based on MongoDB $lookup on array of objects with reference objectId and in the answer from J.F. (data organization). Note that he used id instead of _id
The code is
db.Shipment.aggregate([
{
$lookup: {
from: "Bid",
localField: "id",
foreignField: "shipmentId",
as: "bids"
}
},
{
$lookup: {
from: "user",
localField: "bids.userId",
foreignField: "id",
as: "allUsers"
}
},
{
$set: {
"bids": {
$map: {
input: "$bids",
in: {
$mergeObjects: [
"$$this",
{
user: {
$arrayElemAt: [
"$allUsers",
{
$indexOfArray: [
"$allUsers.id",
"$$this.userId"
]
}
]
}
}
]
}
}
}
}
},
{
$unset: [
"allUsers"
]
},
// to get just one
//{
// $match: {
// "id": 1
// }
// },
])

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

Resources