MongoDB aggregate match by array - mongodb

is there some way to match by an array, using aggregation on mongodb?
I have the following structure:
students
{
_id: 67264264726476,
name: "Student 1",
email: "student1#email.com"
}
followups
{
_id: 65536854685,
student: 67264264726476,
message: "This is a student follow up"
}
I've made the following query:
const followups = await this.modelFollowup.aggregate([
{
$lookup: {
from: 'students',
localField: 'student',
foreignField: '_id',
as: 'student',
},
},
{ $unwind: { path: '$student' } },
{
$match: {
$or: [
{ 'student.name': { $regex: term, $options: 'i' } },
{ 'student.email': { $regex: term, $options: 'i' } },
],
},
},
]);
How can I make it possible to match using an array, instead of the term variable?

If you want to match the complete list of student names and email you can check this query
Query
db.followups.aggregate([
{
$lookup: {
from: "students",
localField: "student",
foreignField: "_id",
as: "student",
},
},
{
$unwind: {
path: "$student"
}
},
{
$match: {
$or: [
{
"student.name": {
"$in": [
"abc",
"def"
]
}
},
{
"student.email": {
"$in": [
"xyz#email.com"
]
}
},
],
},
},
])
Here is the link to the playground to check the query

Related

MongoDB - query references 2 deep of ObjectIDs

I've inherited a Azure Cosmos database with a MongoDB API. There is extensive use of "discriminators" so a single collection has many different models.
I am trying to query a document three levels deep based on document ids (ObjectId())
Parent Group
{
_id: ObjectId(),
__type: "ParentGroup",
name: "group 1",
subgroups: [
...ObjectIds,
],
}
Sub Group
{
_id: ObjectId(),
__type: "SubGroup",
name: "a text name",
members: [
...ObjectIds,
],
}
Member
{
_id: ObjectId(),
__type: "Member",
name: "string",
email: "",
induction: Date,
}
Examples I've seen deal with nested documents NOT references
Is it possible to query the Member documents and return?
[
{
parentGroup,
subgroups: [
{sub group, members: [...members]},
{sub group, members: [...members]},
{sub group, members: [...members]},
]
},
]
After reading the comments and further reading i've got this. Its almost there but I think the limitation of MongoDB will prevent the solution being in a single query. The goal is to return ParentGroups->Subgroups->Members Where Members have an "induction" value of "whatever". I am either returning ALL ParentGroups or nothing at all
db.development.aggregate([
{
$match: {
__type: "ParentGroup", $expr: {
$gt: [
{ $size: "$subgroups" }, 0
]
}
}
},
{
$lookup: {
from: "development",
localField: "subgroups",
foreignField: "_id",
as: "subgroups"
}
},
{
$unwind: {
path: "$subgroups",
// preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: "development",
localField: "subgroups.members",
foreignField: "_id",
as: "subgroups.members"
}
}
])
Solution that worked for me:
db.development.aggregate([
{
$match: {
__type: "ParentGroup",
},
},
{
$lookup: {
from: "development",
localField: "subgroups",
foreignField: "_id",
as: "subgroups",
},
},
{
$unwind: {
path: "$subgroups",
preserveNullAndEmptyArrays: true,
},
},
{
$lookup: {
from: "development",
localField: "subgroups.members",
foreignField: "_id",
as: "subgroups.activities_x",
},
},
{
$unwind: {
path: "$subgroups.members",
preserveNullAndEmptyArrays: true,
},
},
{
$match: { "subgroups.members.meta": { $exists: true } },
},
{
$project: {
_id: 1,
__type: 1,
name: 1,
subgroups: {
_id: 1,
__type: 1,
name: 1,
members: {
_id: 1,
__type: 1,
name: 1,
meta: 1,
},
},
},
},
]);

MongoDB conditional $lookup with aggregration framework

I'm trying to do a conditional lookup with aggregration framework. In my local and foreign collections I have two fields: fieldA and fieldB. If fieldA != 0 my $lookup should be:
{ from: 'collectionA', localField: 'fieldA', foreignField: 'fieldA', as: 'agg' }
Otherwise, if fieldA = 0 my $lookup should be:
{ from: 'collectionA', localField: 'fieldB', foreignField: 'fieldB', as: 'agg' }
Is it possible to combine these conditions with a single $lookup?
It's not really possible OOB, but you can work around this in multiple ways.
for example add a new "temporary" field based on this condition:
db.colleciton.aggregate([
{
$lookup: {
from: 'collectionA',
let: {
fieldA: "$fieldA", fieldB: "$fieldB"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
{
$cond: [
{
$eq: [
'$$fieldA',
0,
],
},
'$$fieldB',
'$$fieldA',
],
},
{
$cond: [
{
$eq: [
'$$fieldA',
0,
],
},
'$fieldB',
'$fieldA',
],
}
],
},
},
},
],
as: 'agg',
},
}
])
The issue with this approach is that indexes won't be utilized for the lookup for older Mongo versions, which in some cases can be crucial.
You can work around for performance purposes like so:
db.collection.aggregate([
{
$facet: {
one: [
{
$match: {
fieldA: { $ne: 0 },
},
},
{
$lookup: { from: 'collectionA', localField: 'fieldA', foreignField: 'fieldA', as: 'agg' },
},
{
$match: {
'agg.0': { $exists: true },
},
},
],
two: [
{
$match: {
fieldA: { $eq: 0 },
},
},
{
$lookup: { from: 'collectionA', localField: 'fieldB', foreignField: 'fieldB', as: 'agg' },
},
{
$match: {
'agg.0': { $exists: true },
},
},
],
},
},
{
$addFieldS: {
combined: {
$concatArrays: [
'$one',
'$two',
],
},
},
},
{
$unwind: '$combined',
},
{
$replaceRoot: {
newRoot: "$combined"
},
},
]);
While there is some overhead here it will still work faster than an unindexed lookup.

mongodb how to query referenced property and return primary collection with referenced property joined

Cat Collection:
{
"_id" : ObjectId("5ee8d0d16e4fec1ad4779249"),
"description" : ObjectId("5ea9af047d6a4f6480fd42f4")
}
Description Collection:
{
"_id" : ObjectId("5ea9af047d6a4f6480fd42f4"),
"color" : "ginger"
}
I'd like to obtain all in Cat collection where the Cat.description.color property equals ginger and the Cat.description property is joined;
{
"_id" : ObjectId("5ee8d0d16e4fec1ad4779249"),
"description" : {
"_id" : ObjectId("5ea9af047d6a4f6480fd42f4"),
"color" : "ginger"
}
}
I have the following aggregate query which works, but seems inefficient due to the second $lookup. Given the $match provides us with the necessary Description objects, is there a better way ?
db.Description.aggregate(
{
$match:{
$and:[
{ color: 'ginger' }
]
}
},
{
$lookup: {
from: 'Cat',
let: { descId: '$_id' },
pipeline: [
{
$match: {
$expr: {
$eq: ['$description', '$$descId']
}
}
}
],
as: 'cat'
}
},
{
$unwind: '$cat'
},
{
$replaceRoot: {
newRoot: '$cat'
}
},
{
$lookup: {
from: 'Description',
localField: 'description',
foreignField: '_id',
as: 'description'
}
},
{
$unwind: '$description'
});
You can aggregate on Cat collection and get the expected result
db.cat.aggregate([
{
$lookup: {
from: "description",
let: { catId: "$description" },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: [ "$_id", "$$catId" ] },
{ $eq: [ "$color", "ginger" ] }
]
}
}
}
],
as: "description"
}
},
])
Working Mongo playground

How to write a $match on a $lookup document in MongoDB

I have two MongoDB collections that look like this:
Products Specials
---------- ----------
_id _id
name product_id
country zip
price percent_discount
out_of_stock
I'm using GraphQL as well so I wrote up an aggregate pipeline that returns the data in this structure:
specials {
_id
product {
_id
name
country
price
}
zip
percent_discount
out_of_stock
}
This aggregate pipeline I wrote works great and looks like this:
let response = await Specials.aggregate([
{
$lookup: {
from: 'products',
localField: 'product_id',
foreignField: '_id',
as: 'product'
}
},
{
$unwind: '$product'
},
{
$match: {
zip: zip
}
}
])
return response;
Now I'm trying to add a filter into this. The filter should match the name or country in the product collection preferably with regex. So I tried writing something like this but it's yielding over 8000 results when there should be only 2-3:
let response = await Specials.aggregate([
{
$match: { zip: zip }
},
{
$lookup: {
from: "products",
let: { product_id: "$product_id" },
pipeline: [
{
$match: {
$expr: {
$and: [
{
_id: "$$product_id"
},
{
$or: [
{
name: filter
},
{
country: filter
}
]
}
]
}
}
}
],
as: "product"
}
},
{
$unwind: "$product"
}
])
If you're using Mongo version 4.2+ you can use $regexMatch
let response = await Specials.aggregate([
{
$lookup: {
from: "products",
let: { product_id: "$product_id" },
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: ["$$product_id", "$_id"]
},
{
$or: [
{
$regexMatch: { input: "$name", regex: filterRegex }
},
{
$regexMatch: { input: "$country", regex: filterRegex }
}
]
}
]
}
}
}
],
as: "product"
}
},
{
$unwind: "$product"
},
{
$match: {
$and: [
{
zip: zip
}
]
}
}
])
return response;
This is the following solution that I was able to get working:
let response = await Specials.aggregate([
{
$lookup: {
from: "products",
let: { product_id: "$product_id" },
pipeline: [
{
$match: {
$and: [
{
$or: [
{
name: { $regex: filter }
},
{
country: { $regex: filter }
}
]
},
{
$expr: {
$eq: ["$$product_id", "$_id"]
}
}
]
}
}
],
as: "product"
}
},
{
$unwind: "$product"
},
{
$match: {
$and: [
{
zip: zip
}
]
}
}
])
return response;
** Updated with the working $regex implementation

$lookup when foreignField is in nested array

I have two collections :
Student
{
_id: ObjectId("657..."),
name:'abc'
},
{
_id: ObjectId("593..."),
name:'xyz'
}
Library
{
_id: ObjectId("987..."),
book_name:'book1',
issued_to: [
{
student: ObjectId("657...")
},
{
student: ObjectId("658...")
}
]
},
{
_id: ObjectId("898..."),
book_name:'book2',
issued_to: [
{
student: ObjectId("593...")
},
{
student: ObjectId("594...")
}
]
}
I want to make a Join to Student collection that exists in issued_to array of object field in Library collection.
I would like to make a query to student collection to get the student data as well as in library collection, that will check in issued_to array if the student exists or not if exists then get the library document otherwise not.
I have tried $lookup of mongo 3.6 but I didn`t succeed.
db.student.aggregate([{$match:{_id: ObjectId("593...")}}, $lookup: {from: 'library', let: {stu_id:'$_id'}, pipeline:[$match:{$expr: {$and:[{"$hotlist.clientEngagement": "$$stu_id"]}}]}])
But it thorws error please help me in regard of this. I also looked at other questions asked at stackoverflow like. question on stackoverflow,
question2 on stackoverflow but these are comapring simple fields not array of objects. please help me
I am not sure I understand your question entirely but this should help you:
db.student.aggregate([{
$match: { _id: ObjectId("657...") }
}, {
$lookup: {
from: 'library',
localField: '_id' ,
foreignField: 'issued_to.student',
as: 'result'
}
}])
If you want to only get the all book_names for each student you can do this:
db.student.aggregate([{
$match: { _id: ObjectId("657657657657657657657657") }
}, {
$lookup: {
from: 'library',
let: { 'stu_id': '$_id' },
pipeline: [{
$unwind: '$issued_to' // $expr cannot digest arrays so we need to unwind which hurts performance...
}, {
$match: { $expr: { $eq: [ '$issued_to.student', '$$stu_id' ] } }
}, {
$project: { _id: 0, "book_name": 1 } // only include the book_name field
}],
as: 'result'
}
}])
This might not be a very good answer, but if you can change your schema of Library to:
{
_id: ObjectId("987..."),
book_name:'book1'
issued_to: [
ObjectId("657..."),
ObjectId("658...")
]
},
{
_id: "ObjectId("898...")",
book_name:'book2'
issued_to: [
ObjectId("593...")
ObjectId("594...")
]
}
Then when you do:
{
$lookup: {
from: 'student',
localField: 'issued_to',
foreignField: '_id',
as: 'issued_to_students', // this creates a new field without overwriting your original 'issued_to'
}
},
You should get, based on your example above:
{
_id: ObjectId("987..."),
book_name:'book1'
issued_to_students: [
{ _id: ObjectId("657..."), name: 'abc', ... },
{ _id: ObjectId("658..."), name: <name of this _id>, ... }
]
},
{
_id: "ObjectId("898...")",
book_name:'book2'
issued_to: [
{ _id: ObjectId("593..."), name: 'xyz', ... },
{ _id: ObjectId("594..."), name: <name of this _id>, ... }
]
}
You need to $unwind the issued_to from library collection to match the issued_to.student with _id
db.student.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(id) } },
{ "$lookup": {
"from": Library.collection.name,
"let": { "studentId": "$_id" },
"pipeline": [
{ "$unwind": "$issued_to" },
{ "$match": { "$expr": { "$eq": [ "$issued_to.student", "$$studentId" ] } } }
],
"as": "issued_to"
}}
])