Lookup based on match result - node.js

I have collection name Services:
[
{
"_id": "61dad1d21aa077c61b7bc2aa",
"name": "HomeMaintenance",
"subServices": [
"61dacb86cb94917c1edcea8f",
"61dad5812881410ba441c401"
],
},
{
"_id": "61dad60b2881410ba441c40e",
"name": "HomeMaintenance",
"subServices": [],
}
]
in another hand I have a subServices Collection like this :
[
{
"_id": "61dacb86cb94917c1edcea8f",
"name": "something",
"title": "something else",
"imageUrl": "",
"__v": 0,
"service": "61dad1d21aa077c61b7bc2aa"
},
{
"_id": "61dad5812881410ba441c401",
"name": "Plumbing",
"title": "Plumbing",
"imageUrl": "",
"__v": 0,
"service": "61dad1d21aa077c61b7bc2aa"
}
]
I came up with a solution with two queries like this
const requestedService = (serviceId)=>{
return servicesModel.findById(id);
};
const ids= requestedService.subServices
const subServicesList = (ids) => {
return subServicesModel.find({
_id: {
$in: ids,
},
});
};
which works perfectly fine, I was wondering is there any way to do these queries with one aggregation pipeline with lookup stage, first find the main services from service collection and then from subServices collection find that subServices of service
something like this
const result = await servicesModel.aggregate([
{
$match: { _id: ObjectId(id) },
},
{
$lookup: {
from: "sub_services",
let: { pid: "$_id" },
pipeline: [
{
$match: {
$expr: {
$in: ["$$pid" //>> id in sub_services modal , //>> "array which we get from match" ],
},
},
},
],
as: "subServices",
},
},
]);

The let is used for declaring the variable from the left document.
Specifies variables to use in the pipeline stages. Use the variable expressions to access the fields from the joined collection's documents that are input to the pipeline.
db.services.aggregate([
{
$match: {
_id: ObjectId(id)
},
},
{
$lookup: {
from: "sub_services",
let: {
subServices: "$subServices"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$_id",
"$$subServices"
]
},
},
},
],
as: "subServices",
},
},
])
Sample Mongo Playground

Related

Posting an aggregation as a new document/collection

In my client I have a form that is sent and stored in Mongo. Made an aggregation to get the name of the people that selected a same place, date and time. Now I would like to create a Mongo document containing all matches as collections so whenever there is a match in place, date and time of people you can get it in a collection. This is what I have so far:
router.get('/match', async (req, res) => {
const matchs = await Forms.aggregate([
{
$group: {
_id: { Date: "$date", Time: "$time", Place: "$place" },
Data: { $addToSet: {Name: "$firstName", Surname:"$surname"}},
count: { $sum: 1 }
}
},
{
$match: {
count: { $gte: 2}
}
},
]);
res.json(matchs)
});
This is the result that I would like to store in Mongo:
{
"_id": {
"Date": "2022-04-20",
"Time": "15:00",
"Place": "Mall"
},
"Data": [
{
"Name": "Carl",
"Surname": "Man"
},
{
"Name": "Christian",
"Surname": "Max"
}
],
"count": 2
}
{
"_id": {
"Date": "2022-04-20",
"Time": "13:00",
"Place": "Restaurant"
},
"Data": [
{
"Name": "Felix",
"Surname": "Sad"
},
{
"Name": "Liu",
"Surname": "Lam"
}
],
"count": 2
}
You can use $out as the last stage in your pipeline. In the following example, matching_collection will contain the result of your pipeline.
{ $out : "matching_collection" }
https://www.mongodb.com/docs/v4.2/reference/operator/aggregation/out/
You can also check $merge, it could be helpful as well.

Aggregate with multiple lookup and pipeline return only the last element

I try below code, it's working to lookup value from other collection. But why it only return the last element.
If I omitted the unwind function, It does return all result from the model, but the second lookup will not working as the first lookup return arrays.
My objective is to look up folder that include the model id which represented in templatefolders collection.
const result = await this.dashboardModel
.aggregate([{ $match: filter }])
.lookup({
from: 'templatefolders',
as: 'template',
let: { id: '$_id' },
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: ['$dashboardId', '$$id'],
},
{
$eq: ['$deletedAt', null],
},
],
},
},
},
{
$project: {
_id: 1,
folderId: 1,
},
},
],
})
.unwind('template')
.lookup({
from: 'folders',
as: 'folder',
let: { folderId: '$template.folderId' },
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: ['$_id', '$$folderId'],
},
{
$eq: ['$deletedAt', null],
},
],
},
},
},
{
$project: {
_id: 1,
name: 1,
},
},
],
})
.unwind('folder')
.exec();
return result;
Result
{
"data": [
{
...(parent field)
"template": {
"_id": "60ab22b03b39e40012b7cc4a",
"folderId": "60ab080b3b39e40012b7cc41"
},
"folder": {
"_id": "60ab080b3b39e40012b7cc41",
"name": "Folder 1"
}
}
],
"meta": {},
"success": true,
"message": "Succesfully get list"
}
I came from Front end background. I hope my question is not a silly one.
Thanks!
EDIT:
dashboard: [{
_id: dashboard1
}]
templatefolders: [{
dashboardId: dashboard1,
folderId: folder123
}]
folders: [{
_id: folder123
}]
You can use $lookup to join collections
$lookup to join two collections .Lookup doc
$unwind to deconstruct the array. Unwind doc
$group to reconstruct the array which we already deconstructed Group doc
Here is the code
db.dashboard.aggregate([
{
"$lookup": {
"from": "templatefolders",
"localField": "_id",
"foreignField": "dashboardId",
"as": "joinDashboard"
}
},
{
"$unwind": "$joinDashboard"
},
{
"$lookup": {
"from": "folders",
"localField": "joinDashboard.folderId",
"foreignField": "_id",
"as": "joinDashboard.joinFolder"
}
},
{
"$group": {
"_id": "$_id",
"joinDashboard": {
"$push": "$joinDashboard"
}
}
}
])
Working Mongo playground

Can't join collections with ids in mongodb

I am trying to get all requests of a user from the request collection based on request status. I am trying to lookup the collection but it doesn't work. Is there any solution to work it out.
Here is my code:
Users.aggregate([
{
$lookup: {
from: 'requests',
let: {userId: '$userId', status: '$status'},
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$_id', '$$userId'] },
{ $eq: ['$$status', 1] }
]
},
}
}
],
as: 'requests'
}
}
]).exec()
I think { $eq: ['$_id', '$$userId'] } is not working. I tried using $toObjectId but still same result.
Here is test data for users:
{
"_id": {
"$oid": "5f1c0112ad207a13308a3fea"
},
"createDate": {
"$date": "2020-07-25T09:52:58.678Z"
},
"userRole": 10,
"status": 1,
"fullName": "Test Name",
"email": "test.name#mailinator.com",
"password": "$2b$10$HQN//qFTQKW8tBnf7G0OV.Uta0zNbxd1hPlGVwvLp5CVIf49Y5PNW",
"__v": 0,
"profileImage": "1595957619296.jpg"
}
And test request data:
{
"_id": {
"$oid": "5f2178c578153018ca5d79e8"
},
"request": "This is a demo request.",
"userId": {
"$oid": "5f1c0112ad207a13308a3fea"
},
"createDate": {
"$date": "2020-07-28T18:30:00.000Z"
},
"status": 1
}
There are few fixes in your query,
$match your user document status is 1, if you don't want then your can exclude,
db.users.aggregate([
{
$match: {
status: 1
}
},
$lookup with requests
let userId it is from user collection so add user collection _id so its corrected,
no need to create status variable because we already checked condition for in above
{
$lookup: {
from: "requests",
let: {
userId: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
$eq check first is requests collection $userId and second is that we have created variable above in let and use $$userId because its reference to main collection users let variables
{
$eq: [
"$userId",
"$$userId"
]
},
second $eq check $status is 1 in requests collection, and you did it in user collection we we have already used in above $match condition
{
$eq: [
"$status",
1
]
}
]
}
}
}
],
as: "requests"
}
}
])
Have divided in parts for explanation purpose you can merge it as they are in sequence,
Working Playground: https://mongoplayground.net/p/THU6HeyqMN4

Joining mongodb collection based on condition [duplicate]

This question already has an answer here:
Conditional $lookup in MongoDB?
(1 answer)
Closed 3 years ago.
I have two collections in MongoDB and want to join the two collections based on some condition.
I want to join 'order' and 'order-status' table to get all orders assigned to '123' with status 'ready'
orders
{
"_id":"1",
"name": "Fridge",
"assignee": "123"
},
{
"_id":"2",
"name": "TV",
"assignee": "567"
},
{
"_id":"3",
"name": "Music system",
"assignee": "123"
}
order-status
{
"_id":"1",
"status": "ready",
"orderId": "1"
},
{
"_id":"2",
"status": "cancelled",
"orderId": "2"
},
{
"_id":"3",
"status": "cancelled",
"orderId": "3"
}
assignee
{
"_id":"123",
"name": "Jak"
}
{
"_id":"567",
"name": "Mac"
}
I want to join 'order' and 'order-status' table to get all orders assigned to '123' with status 'ready'
Expecting a final result as
[
{
"_id":"1",
"name": "Fridge",
"assignee": "123",
"status": {
"_id":"1",
"status": "ready",
"orderId": "1"
}
}
]
Tried following but how to check order status in another table with lookup
const resultObject = orders.aggregate([
{ $match : {assignee: Objectid('123')} },
{
$lookup: {
from: 'user-status',
localField: 'assignee',
foreignField : '_id',
as : 'assignee'
}
},
{
$unwind: '$assignee'
}
]);
First you need to use match to filter by "assignee": "123", then you need to lookup order-status, match "orderStatus.status": "ready".
const resultObject = orders.aggregate([
{
$match: {
assignee: "123"
}
},
{
$lookup: {
from: "order-status",
localField: "_id",
foreignField: "orderId",
as: "statuses"
}
},
{
$match: {
"statuses.status": "ready"
}
},
{
$project: {
id: "_id",
name: "$name",
assignee: "$assignee",
status: {
$arrayElemAt: ["$statuses", 0]
}
}
}
]);
This will give result like this:
[
{
"_id": "1",
"assignee": "123",
"name": "Fridge",
"status": {
"_id": "1",
"orderId": "1",
"status": "ready"
}
}
]
Playground
I would use the following pipeline:
const resultObject = orders.aggregate([
{
$match: {
assignee: Objectid('123')
}
},
{
$lookup:
{
from: "order-status",
let: {order_id: "$_id"},
pipeline: [
{
$match:
{
$expr:
{
$and:
[
{$eq: ["$orderId", "$$order_id"]},
{$eq: ["$status", "ready"]}
]
}
}
}
],
as: "stock"
}
},
{
$unwind: "$stock"
},
// now we get the assignee info.
{
$lookup: {
from: 'user-status',
localField: 'assignee',
foreignField: '_id',
as: 'assignee'
}
},
{
$unwind: '$assignee'
},
//finaly create the required structure.
{
$project: {
name: "$assignee.name",
assignee: "$assignee._id",
status: "$stock.0"
}
}
]);

add key to nested array with condition

I have a simple datastructure in mongodb:
{
_id: ObjectID,
name: 'Name',
birthday: '25.05.2001'
items: [
{
_id: ObjectID,
name: 'ItemName',
info: 'ItemInfo',
},
{
_id: ObjectID,
name: 'ItemName',
info: 'ItemInfo',
}
]
}
Now i want a query, that takes a ObjectID (_id) of an item as criteria and gives me back the object with all items in the array AND projects a new field "selected" with value true or false into a field in the result of each array item:
I tried that with this query:
{ $unwind: '$items' },
{
$project: {
selected: {
$cond: { if: { 'items._id': itemObjectID }, then: true, else: false },
},
},
},
but MongoDB gives me back an error:
MongoError: FieldPath field names may not contain '.'.
Have no clue why its not working, any help or ideas? Thank you very much!
What you are missing here is $eq aggregation operator which checks the condition for the equality.
You can try below aggregation here if you want to check for ObjectId then you need to put mongoose.Types.ObjectId(_id)
db.collection.aggregate([
{ "$unwind": "$items" },
{ "$addFields": {
"items.selected": {
"$eq": [
1111,
"$items._id"
]
}
}},
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"items": {
"$push": {
"_id": "$items._id",
"selected": "$items.selected"
}
}
}}
])
Will give following output
[
{
"_id": ObjectId("5a934e000102030405000000"),
"items": [
{
"_id": 1111,
"selected": true
},
{
"_id": 2222,
"selected": false
}
],
"name": "Name"
}
]
You can check it here
#Ashish: Thank you very much for your help! Your answer helped me to build the right query for me:
db.collection.aggregate([
{
$unwind: "$items"
},
{
$project: {
"items.name": 0,
"birthday": 0
}
},
{
"$addFields": {
"items.selected": {
"$eq": [
1111,
"$items._id"
]
}
}
},
{
$group: {
_id: "$_id",
"name": {
"$first": "$name"
},
items: {
$push: "$items"
}
}
},
{
$match: {
"items._id": {
$eq: 1111
}
}
},
])
and leads to a result that looks like:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"items": [
{
"_id": 1111,
"selected": true
},
{
"_id": 2222,
"selected": false
}
],
"name": "Name"
}
]

Resources