MongoDB: $match on a nested document after $lookup - node.js

I need to query a collection of timecards which are linked to jobs, while jobs are linked to facilities with a specific facility ID. I have the following aggregation query:
Timecard.aggregate([
{ $match: query }, //some query parameters set before, doesn't matter here
{
$lookup: {
from: 'jobs',
as: 'job',
localField: 'job',
foreignField: '_id'
}
},
{ $unwind: '$job' },
{
$lookup: {
from: 'facilities',
localField: 'job.facilityId'
foreignField: '_id',
as: 'job.facilityId'
}
},
{ $unwind: '$job.facilityId' },
{ $match: {'job.facilityId._id':'5cad048d95d61a002f5a9edb'}}
];)
Everything before the last $match works just fine, jobs are populated into timecards, facilities are populated into jobs. But the match doesn't work for some reason. However, if I add facilityId field with
{$addFields: {facilityId: '$job.facilityId._id'}}
and change the $match to
{$match: 'facilityId':'5cad048d95d61a002f5a9edb'}
then it works.
The question is, is there a way to query a subdocument's field with dot notation this way?

Your last line likely needs to be an ObjectId
{ $match: {'job.facilityId._id': ObjectId('5cad048d95d61a002f5a9edb')}}

Related

get _id based on value from a collection for a array and insert in mongodb collection

Input:
[{"gradeName":1,"sectionName":"a","maximumStudents":12},{"gradeName":2,"sectionName":"b","maximumStudents":13}]
In mongodb how to get the _id of gradeName and sectionName from two collection and store in the third collection say mergeddetails
mergeddetails:
[{"grade_id":"60f819e04a9d43158cabad55","section_id":"60f819e04a9d43158cabad54","maximumStudents":12},{"grade_id":"60f819e04a9d43158cabad59","section_id":"60f819e04a9d43158cabad5b","maximumStudents":13}]
where grade_id is the _id of corresponding gradeName,section_id is the _id of corresponding sectionName
please help with with mongodb quire i am using nodejs to write the API
You need to use $lookup for both the collections
db.student.aggregate([
{ $lookup:
{ from: "grade",
localField: "gradeName",
foreignField: "grade",
as: "grade"
}
},
{$lookup:
{ from: "section",
localField: "sectionName",
foreignField: "section",
as: "section"
}
},
{ $unwind: "$grade" },
{ $unwind: "$section" },
{$project:
{
_id:0,
"grade_id":"$grade._id",
"section_id":"$section._id",
maxStudent:1
}
}
])

NodeJs mongoose performance slow when compared with mongo shell or robo3t performance

I am running a query that prints around 300 results using aggregate.
The query is super fast when executed in robo3t/mongo shell but super slow when done using mongoose in NodeJs.
Tried finding for an answer, landed on this link tried setting DBQuery.shellBatchSize = 500000; but difference b/w two queries is still around 7-8 secs
Also, updated the mongoose and mongodb version to the latest one today itself.
here is the query:
db.getCollection('fixed_activities').aggregate([
{
$lookup: {
from: "city",
localField: "activity_city",
foreignField: "_id",
as: "activity_location"
}
},
{
$lookup: {
from: "rate_sheet_activity_prices",
localField: "_id",
foreignField: "act_id",
as: "rate_sheets"
}
},
{
$lookup: {
from: "organizations",
localField: "rate_sheets.rate_sheet_id",
foreignField: "rate_sheets.rate_sheet_id",
as: "org_id"
}
},
{
$lookup: {
from: "images",
localField: "activity_image",
foreignField: "_id",
as: "activity_image"
}
},
{ $match: { org_id: { $exists: true, $not: { $size: 0 } } } }
])
I had the same problem, my solution was to switch from mongoose to the native mongodb controller in nodejs.
https://mongodb.github.io/node-mongodb-native/3.3/installation-guide/installation-guide/

Fetch Mongodb data with custom filters from multiple tables

I am getting data from Mongodb like this.
orders = await ordersCollection
.find(rest)
.sort({ dateAdded: -1 })
.skip(parseInt(skip))
.limit(parseInt(limit))
.toArray();
This query returns data in the orders collection. But in the orders collection I have a userID and I have users table. There is _Id of every user is listed with some of the other data like email. I need that email field. How might it be possible? I have tried $lookup and aggregate but no luck yet. Maybe there is some other technique to use with filters, like sort,skip,limit.
here is both collections look like.
orders collection
{"_id":"5d7f276d86250800408856c5","type":"admin-credit",
"userId":"5d7f074644de5a00b1c0d5c6",
"fromAccount":"",
"toAccount":"AUD",
"amount":20,"fee":0,
"quote":null,
"linkId":null,
"dateAdded":"2019-09-16T06:10:53.554Z",
"dateApproved":"2019-09-16T06:10:53.614Z",
"orderStatus":"approved",
"adminId":"5d7f05b71450370072212b89"}
Users collection
{"_id":"5d7f074644de5a00b1c0d5c6",
"email":"pardeep889#hotmail.com",
"hash":"$2a$10$ocdE3gwsGZR8N5Yauhg1MeKIU11UkKrHynnYWB24x/TWR2WhM/nIu",
"confirmationToken":"e7e0ca1a-cc73-4691-8472-"
"ipAddrs":[{"ip":"192.180.2.15",
"date":"2019-09-16T03:54:03.972Z"}],
"socketToken":"c6a7fc7c-f983-439a-bafa-e93618e0a4e8"
}
how I am trying to get the data.
orders = await ordersCollection
.find(rest)
.sort({ dateAdded: -1 })
.skip(parseInt(skip))
.limit(parseInt(limit))
.aggregate([
{
$lookup: {
from: "users",
localField: "user_id",
foreignField: "_id",
as: "new record"
}
}])
.toArray();
I need the email field with the order.
Can use $sort and $limit in the aggregation pipeline if need be. Also note that I've added $match for the filter condition:
db.orders.aggregate([
{ $match: {} },
{
$lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "userDoc"
}
},
{ $unwind: "$userDoc" },
{
$project: {
_id: 1,
type: 1,
userId: 1,
fromAccount: 1,
toAccount: 1,
amount: 1,
fee: 1,
quote: 1,
linkId: 1,
dateAdded: 1,
dateApproved: 1,
orderStatus: 1,
adminId: 1,
email: "$userDoc.email"
}
}
]);
NOTE: $project is used to return all fields from orders and only email from joined collection documents.

Join a collection to an existing aggregate query mongodb

I have the following collection structures in my mongodb database.
Orders
[
{
"_id":"order_abcd",
"name":"Order 1"
},
{
"_id":"order_defg",
"name":"Order 2"
}
]
Session
{
"_id":"session_abcd"
"orders": [ ObjectId("order_abcd"), ObjectId("order_defg") ]
}
Transactions
{
"_id":"transaction_abcd"
"id_session" : ObjectId("session_abcd")
}
What I am trying to achieve is a dataset that looks similar to this
[
{
"_id":"order_abcd",
"name":"Order 1",
"transaction":"transaction_abcd"
},
{
"_id":"order_defg",
"name":"Order 2",
"transaction":"transaction_abcd"
}
]
I do not have any input data other than a start date and an end date which will be used to filter orders, the query is mostly for reporting purposes so in effect I am trying to generate a query to fetch all orders between a given time period and attach the transaction id for each order to it.
We can use couple of $lookup (analogous to join with 2 tables in SQL) with $unwind at each stage to finally $project the key-value pair that is desired.
db.Session.aggregate([
{
$lookup: {
from: "Transactions",
localField: "_id",
foreignField: "id_session",
as: "transaction_info"
}
},
{ $unwind: "$transaction_info" },
{
$lookup: {
from: "Orders",
localField: "orders",
foreignField: "_id",
as: "order_info"
}
},
{ $unwind: "$order_info" },
{$project:{
_id:"$order_info._id",
name:"$order_info.name",
transaction:"$transaction_info._id"
}}
]).pretty();
Which gives an output:
{
"_id" : "order_abcd",
"name" : "Order 1",
"transaction" : "transaction_abcd"
},
{
"_id" : "order_defg",
"name" : "Order 2",
"transaction" : "transaction_abcd"
}
The unwind stages are used to explode the lookup and then cherry pick fields at final project stage.
++UPDATE++
Another option that probably can help reduce the 2nd stage lookup records since $match on dates of Orders can be applied to pass on filtered docs for next stage.
db.Session.aggregate([
{
$lookup: {
from: "Orders",
localField: "orders",
foreignField: "_id",
as: "order_info"
}
},
{ $unwind: "$order_info" },
{
$match: {} //filter on "order_info.property" (i:e; date,name,id)
},
{
$lookup: {
from: "Transactions",
localField: "_id",
foreignField: "id_session",
as: "transaction_info"
}
},
{ $unwind: "$transaction_info" },
{
$project: {
_id: "$order_info._id",
name: "$order_info.name",
transaction: "$transaction_info._id"
}
}
]).pretty();

Mongoose compare with a third level nested field value

I have the following collections:
and I would like to do a find from collection1 all the way to collection 3 and 4 to compare name in one query.
example:
collection1.find({
collection2.collection3.name: req.body.name3,
collection2.collection4.name: req.body.name4
}).exec()
You need to use lookup, unwind and match, here is untested solution of your problem
Model.aggregate([
{
$match: {
_id: req.params.id
// for object id use
// _id: mongoose.Types.ObjectId(req.params.id)
}
},
{
$lookup: {
from: "collection2",
localField: "collection2",
foreignField: "_id",
as: "Collection2"
}
},
{
$unwind: "$ColelctionTwo"
},
{
$lookup: {
from: "collection3",
localField: "CollectionTwo.collection3",
foreignField: "_id",
as: "Collection3"
}
},
{
$lookup: {
from: "collection4",
localField: "CollectionTwo.collection4",
foreignField: "_id",
as: "Collection4"
}
}
]).exec(function(err, result) {
if(err) {
// handle here
}
if(result) {
// do something here...
}
}
You can use mongodb aggregate framework's $lookup or $graphlookup stages for multi collection queries. Mongoose docs for aggregation https://mongoosejs.com/docs/api.html#Aggregate
You cannot do a simple find query for multi collection lookups.

Resources