mongodb lookup with foreign field match - mongodb

I want to know how can i lookup with match.
I could lookup two collection by this query.
db.category.aggregate([
{
$lookup:
{
from: "faq",
localField: "_id",
foreignField: "category_code",
as: "faq"
}
}
])
Then i get this result.
{
"_id":ObjectId("1234"),
"category_name":"about account",
"faq": [
{
"_id":ObjectId("faq id blah blah"),
"category_code" : ObjectId("1234"),
"faq_title":"When you can't create account",
"del_flg":"N"
},
{
"_id":ObjectId("faq id blah blah2222"),
"category_code" : ObjectId("1234"),
"faq_title":"When you change your account",
"del_flg":"N"
},
{
"_id":ObjectId("faq id blah blah3333"),
"category_code" : ObjectId("1234"),
"faq_title":"When you lost your account",
"del_flg":"Y"
}
[
}
I want just two faq item which "del_flg" is "N"
I am not familiar to use mongodb.
Is it possible if i use pipline? I tried some pipline examples, but i couldn't.

You should try $lookup with pipeline,
let will create a variable to access id inside lookup pipeline using $$
pipeline match using $expr because we are comparing both fields $$id = $category_code
set match condition del_flg: N
db.category.aggregate([
{
$lookup: {
from: "faq",
let: {
id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$$id",
"$category_code"
]
},
"del_flg": "N"
}
}
],
as: "faq"
}
}
])
Playground: https://mongoplayground.net/p/gUVUMk6KIAV

Related

$lookup with pipeline match and projection does not work for guid

I have two collections that I want to join with $lookup based on two id fields. Both fields are from type guid and looke like this in mongodb compass: 'Binary('cavTZa/U2kqfHtf08sI+Fg==', 3)'
This syntax in the compass aggregation pipeline builder gives the expected result:
{
from: 'clients',
localField: 'ClientId',
foreignField: '_id',
as: 'ClientData'
}
But i want to add some projection and tried to change it like this:
{
from: 'clients',
'let': {
id: '$_id.clients'
},
pipeline: [
{
$match: {
$expr: {
$eq: [
'$ClientId',
'$$id'
]
}
}
},
{
$project: {
Name: 1,
_id: 0
}
}
],
as: 'ClientData'
}
But the result here is that every client from collection 'clients' is added to every document in the starting table. I have to use MongoDB 3.6 so the new lookup syntax from >=5.0 is not available.
Any ideas for me? Does $eq work for binary stored guid data?
In the first example, you say that the local field is ClientId and the foreign field is _id. But that's not what you used in your second example.
This should work better:
{
from: 'clients',
'let': {
ClientId: '$ClientId'
},
pipeline: [
{
$match: {
$expr: {
$eq: [
'$$ClientId',
'$_id'
]
}
}
},
{
$project: {
Name: 1,
_id: 0
}
}
],
as: 'ClientData'
}

get collection data based on id fetched in listing in nested lookups and conditional lookup in mongodb aggregation

I just need following output
{
message: "Details are:",
status: 1,
data:[
{
leadId: "92106",
projectName: "Sales Rep Mobile App with Shopify Backend"
projectOverview: "<any description>"
notificationsData:[
{
_id:"6076e2593580d805814c338e",
content:"<strong>User Jangid</strong> posted a comment about this message on estimation portal.",
estimationId:"5f75a496c70f05559088d971",
commentId:"6076e2583580d805814c338b",
commentData:{
_id:"6076e2583580d805814c338b",
content:"<p>hello krishna this is and this is second </p>\n"
}
},
{
_id:"6077c7c75c1bfc051f8dff3e",
content:"<strong>User Nunna</strong> posted a comment about this message on estimation portal.",
estimationId:"5f75a496c70f05559088d971",
commentId:"6077c7c35c1bfc051f8dff3b",
commentData:{
_id:"6077c7c35c1bfc051f8dff3b",
content:"<p>hey hiiii</p>\n\n<p> </p>\n"
}
},
],
userName:"user Nunna",
profileImage:"profile url",
isViewed:true,
isSortByStatus:2,
isNotifyEst:1
}
]
}
In the above example i have multiple list of same type of collection
and this list is i aggregate with Estimation Collection
These Above Data if fetched based on Estimation Collection
so based on this Estimation Collection's _id i need to fetch notification and based on commentId that fetched inside notification collection i need to fetch comments collection data.
i already do aggregation with some more collection i only need the notification and comment data inside notificationData array
Yes i Achieve this i use following query for that
let estData = await Estimations.aggregate([
{
$lookup: {
from: "notifications", as: "notificationsData",
let: {
estimation_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$estimationId', "$$estimation_id"] },
{ $eq: ['$userId', ObjectId(this.req.currentUser._id)] },
{ $eq: ['$isViewed', false] },
]
}
},
},
{
$lookup:
{
from: "comments",
localField: "commentId",
foreignField: "_id",
as: "commentData"
}
},
{ "$unwind": "$commentData" },
],
}
},
{
$project: {
projectName: 1,
"notificationsData.content": 1,
"notificationsData.estimationId": 1,
"notificationsData._id": 1,
"notificationsData.commentId": 1,
"notificationsData.commentData.content": 1,
"notificationsData.commentData._id": 1,
leadId: 1,
projectOverview: 1
}
},
])
in the above query i get notificationData based on estimationId and commentData data based on commentId that is specified inside notificationData
so here it is solution that i do and works fine.

MongoDB lookup with object relation instead of array

I have a collection matches like this. I'm using players object {key: ObjectId, key: ObjectID} instead of classic array [ObjectId, ObjectID] for reference players collection
{
"_id": ObjectId("5eb93f8efd259cd7fbf49d55"),
"date": "01/01/2020",
"players": {
"home": ObjectId("5eb93f8efd259cd7fbf49d59"),
"away": ObjectId("5eb93f8efd259cd7fbf49d60")
}
},
{...}
And players collection:
{
"_id": ObjectId("5eb93f8efd259cd7fbf49d59"),
"name": "Roger Federer"
"country": "Suiza"
},
{
"_id": ObjectId("5eb93f8efd259cd7fbf49d60"),
"name": "Rafa Nadal"
"country": "España"
},
{...}
What's the better way to do mongoDB lookup? something like this is correct?
const rows = await db.collection('matches').aggregate([
{
$lookup: {
from: "players",
localField: "players.home",
foreignField: "_id",
as: "players.home"
}
},
{
$lookup: {
from: "players",
localField: "players.away",
foreignField: "_id",
as: "players.away"
},
{ $unwind: "$players.home" },
{ $unwind: "$players.away" },
}]).toArray()
I want output like this:
{
_id: 5eb93f8efd259cd7fbf49d55,
date: "12/05/20",
players: {
home: {
_id: 5eb93f8efd259cd7fbf49d59,
name: "Roger Federer",
country: "Suiza"
},
away: {
_id: 5eb93f8efd259cd7fbf49d60,
name: "Rafa Nadal",
country: "España"
}
}
}
{...}
You can try below aggregation query :
db.matches.aggregate([
{
$lookup: {
from: "players",
localField: "players.home",
foreignField: "_id",
as: "home"
}
},
{
$lookup: {
from: "players",
localField: "players.away",
foreignField: "_id",
as: "away"
}
},
/** Check output of lookup is not empty array `[]` & get first doc & write it to respective field, else write the same value as original */
{
$project: {
date: 1,
"players.home": { $cond: [ { $eq: [ "$home", [] ] }, "$players.home", { $arrayElemAt: [ "$home", 0 ] } ] },
"players.away": { $cond: [ { $eq: [ "$away", [] ] }, "$players.away", { $arrayElemAt: [ "$away", 0 ] } ] }
}
}
])
Test : mongoplayground
Changes or Issues with current Query :
1) As you're using two $unwind stages one after the other, If anyone of the field either home or away doesn't have a matching document in players collection then in the result you don't even get actual match document also, But why ? It's because if you do $unwind on [] (which is returned by lookup stage) then unwind will remove that parent document from result, To overcome this you need to use preservenullandemptyarrays option in unwind stage.
2) Ok, there is another way to do this without actually using $unwind. So do not use as: "players.home" or as: "players.away" cause you're actually writing back to original field, Just in case if you don't find a matching document an empty array [] will be written to actual fields either to "home" or "away" wherever there is not match (In this case you would loose actual ObjectId() value existing in that particular field in matches doc). So write output of lookup to a new field.
Or even more efficient way, instead of two $lookup stages (Cause each lookup has to go through docs of players collection again & again), you can try one lookup with multiple-join-conditions-with-lookup :
db.matches.aggregate([
{
$lookup: {
from: "players",
let: { home: "$players.home", away: "$players.away" },
pipeline: [
{
$match: { $expr: { $or: [ { $eq: [ "$_id", "$$home" ] }, { $eq: [ "$_id", "$$away" ] } ] } }
}
],
as: "data"
}
}
])
Test : mongoplayground
Note : Here all the matching docs from players which match with irrespective of away or home field will be pushed to data array. So to keep DB operation simple you can get that array from DB along with actual matches document & Offload some work to code which is to map respective objects from data array to players.home & players.away fields.

Mongo Aggregate Objects with $lookup using non matching values

I've got an Object Mission referring to another object Position with a key _p_position.
Mission objects look like:
{
_id: "ijjn97678",
_p_position: "Position$qwerty123",
...
}
Position objects look like:
{
_id: "qwerty123",
...
}
I don't know if it is Mongo or Parse convention but as one can see a Position$ is added on relational position attribute in missions.
I'd like to aggregate both into a single Object to get a results similar to the following:
{
_id: "ijjn97678",
_p_position: "Position$qwerty123",
positions: [
{
_id: "qwerty123"
}
]
}
using:
missions.aggregate([
{
$lookup: {
as: "position",
from: "Position",
foreignField: "_id",
localField: "_p_position",
},
},
])
But I need to remove Position$ from _p_position. Is there a way I can compute "_p_position" before it is used to find a matching Position's id ?
PS: I only have reading rights on DB
You can use $addFields to add another field which will be then passed to $lookup stage. To get the part that's following the dollar sign you need: $indexOfBytes and $substr operators. Additionally dollar sign itself is a special character in Aggregation Framework (represents a field reference) so you need $literal to force it to be considered as regular field
db.missions.aggregate([
{
$addFields: {
value: {
$let: {
vars: { index: { $indexOfBytes: [ "$_p_position", { $literal: "$" } ] } },
in: { $substr: [ "$_p_position", { $add: [ "$$index", 1 ] } , { $strLenBytes: "$_p_position" } ] }
}
}
}
},
{
$lookup: {
from: "Position",
localField: "value",
foreignField: "_id",
as: "position"
}
}
])

When I search on multiple field included lookup with more then one field with $regex MongoDB perform slow

I have Book collection which have Name, Description, Publisher ObjectID field, Array of Authors ID, Categories ID field etc. I need to search book with name, description, publisher name, author and category name with $regex.
To do that in aggregation pipe first I populate authors, publisher, categories with $lookup and then use $match with $or operator over the field.
My query works but it perform very slow ( approximately 11s ) where Book collection contain only 70 thousand documents.
What steps should I need in collection model, Indexing or Query good performance?
Book Model:
{
"_id" : ObjectId("5a2934934410bf8b0e547989"),
"publisher" : ObjectId("5a27e7b68021772210b125d4"),
"is_enabled" : true,
"published_at" : ISODate("2017-12-07T12:31:15.166Z"),
"author" : [
ObjectId("5a27c5754b0efc477f37a131"),
ObjectId("5a27c5754b0efc47737a1512"),
ObjectId("5a27c5754b0efc477f37a145"),
],
"category" : [
ObjectId("5a27e22ffb6110b11c326cd7"),
ObjectId("5a27e22ffb6110b11c326ced"),
ObjectId("5a27e22ffb6110b11c326d2d"),
ObjectId("5a27e22ffb6110b11c326e45")
]
"published_year" : "2017"
}
Query I executed:
Book.aggregate(
[
{
$match: {
"is_enabled": { $eq: true },
}
},
{
$lookup:
{
from: "authors",
localField: "author",
foreignField: "_id",
as: "author"
}
},
{
$lookup:
{
from: "categories",
localField: "category",
foreignField: "_id",
as: "category"
}
},
{
$lookup:
{
from: "publishers",
localField: "publisher",
foreignField: "_id",
as: "publisher"
}
},
{
$match: {
$or: [
{ "author.name": new RegExp(params.expression, 'i') },
{ "category.name": new RegExp(params.expression, 'i') },
{ "publisher.name": new RegExp(params.expression, 'i') },
{ "description": new RegExp(params.expression, 'i') },
{ "name": new RegExp(params.expression, 'i') },
{ "published_year": params.terms }
]
}
},
{
$project: {
previous_price: "$previous_price",
price: "$price",
name: "$name",
seo_url: "$seo_url",
click_url: "book",
author: "$author",
authorObj: {
name: { $arrayElemAt: ["$author.name", 0] },
}
}
},
{ $sort: { name: 1 } }
]
)
.skip(8 * (params.pagenum - 1))
.limit(8)
.exec((err, product) => {
if (err)
reject(err);
else
resolve(product);
})
You can create index for fields is_enabled, author, category and publisher like bellow.
db.coll.createIndex( { is_enabled: 1 } )
db.coll.createIndex( { author: 1 } )
db.coll.createIndex( { category: 1 } )
db.coll.createIndex( { publisher: 1 } )
that will increase the performance for first match stage and for lookup.
you can also create index for name, description and published_year but I am not sure how will affect of this index for last match stage because you used $or condition. As far I know still unable to optimize indexed queries that uses $or, $in (<=3.2). You can try that. It will be helpful if you use $and condition query. If you use $and query then you can also create multi key index for name, description and published_year. like
db.coll.createIndex( { name: 1, description: 1 published_year:1 } )
and then you should follow the same order in match condition
{$match: { name: 'xx', description:'yy', published_year: 2017}}