Find({ example: { $elemMatch: { $eq: userId } } }).. in Aggregate - is it possible? - mongodb

database:
[{to_match: [ userID_1, userId_2 ], data: [{...}] },
{to_match: [ userID_1, userId_2, userId_3 ], data: [{...}] },
{to_match: [ ], data: [{...}] }]
Find by an element in the array 'to-match'.
Current solution:
Replacement.find(
{ applicants: { $elemMatch: { $eq: userId_1 } } },
Aggregate $lookup on the result of 1.
a. Can I Find and Aggregate?
b. Should I first Aggregate and then match ??
if yes, how to match on the element in the array?
I tried Aggregate:
$lookup // OK
{ $match: { applicants: { $in: { userId } } } } // issues
Thank you

Use $lookup and $match in aggregate
Instead of $in use $elemMatch like below:
{ $match: { applicants: { $elemMatch: { $eq: userId_1 } } } }

Doing an $elemMatch on just one field is equivalent to using find(link)
Generally, it is efficient to limit the data that the lookup stage will be working on.
So if I understand correctly, you want to filter the "to_match" array elements and then do a lookup on that result.
Here is what I would suggest:-
aggregate([
{
$project : {
to_match: {
$filter: {
input: "$to_match",
as: "item",
cond: { $eq: [ "$$item", "userId_3" ] }
}
},
data : 1
}
},
{
$match : { "to_match" : {$ne : []}}
},
//// Lookup stage here
])
Based on the field that you want to do a lookup on, you may want to unwind this result.

Related

Filtering array items from items in another array in mongodb aggregation

I have managed to create an aggregate which returns two array in a document like so:
"b": [
{
"_id": "6258bdfe983a2d31e1cc6a4b",
"booking_room_id": "619395ba18984a0016caae6e",
"checkIn_date_time": "2022-04-16",
"checkOut_date_time": "2022-05-17"
}
]
"r": [
{
"_id": "619395ba18984a0016caae6e",
}
]
I want to remove the item from r if _id in r matches booking_room_id in b.
Also, since these array exist inside a parent document. I want to remove the parent document from the query if r is empty after performing the filter.
Use $expr and $filter
db.collection.aggregate([
{
$match: {
$expr: {
$ne: [
{
$filter: {
input: "$r",
as: "r",
cond: {
$not: { $in: [ "$$r._id", "$b.booking_room_id" ] }
}
}
},
[]
]
}
}
},
{
$set: {
r: {
$filter: {
input: "$r",
as: "r",
cond: {
$not: { $in: [ "$$r._id", "$b.booking_room_id" ] }
}
}
}
}
}
])
mongoplayground

$filter inside $reduce or inside $map from array without unwind

I need some help:
I want to optimize this query to be faster , it need to filter by events.eventType:"log" all docs with server:"strong" , but without separate unwind & filter stages , maybe somehow inside the $reduce stage to add $filter.
example single document:
{
server: "strong",
events: [
{
eventType: "log",
createdAt: "2022-01-23T10:26:11.214Z",
visitorInfo: {
visitorId: "JohnID"
}
}
current aggregation query:
db.collection.aggregate([
{
$match: {
server: "strong"
}
},
{
$project: {
total: {
$reduce: {
input: "$events",
initialValue: {
visitor: [],
uniquevisitor: []
},
in: {
visitor: {
$concatArrays: [
"$$value.visitor",
[
"$$this.visitorInfo.visitorId"
]
]
},
uniquevisitor: {
$cond: [
{
$in: [
"$$this.visitorInfo.visitorId",
"$$value.uniquevisitor"
]
},
"$$value.uniquevisitor",
{
$concatArrays: [
"$$value.uniquevisitor",
[
"$$this.visitorInfo.visitorId"
]
]
}
]
}
}
}
}
}
}
])
expected output , two lists with unique visitorId & list of all visitorId:
[
{
"total": {
"uniquevisitor": [
"JohnID"
],
"visitor": [
"JohnID",
"JohnID"
]
}
}
]
playground
In the example query no filter is added for events.eventType:"log" , how can this be implemented without $unwind?
I am not sure this approach is more optimized than yours but might be this will help,
$filter to iterate loop of events and filter by eventType
$let to declare a variable events and store the above filters result
return array of visitor by using dot notation $$events.visitorInfo.visitorId
return array of unique visitor uniquevisitor by using dot notation $$events.visitorInfo.visitorId and $setUnion operator
db.collection.aggregate([
{ $match: { server: "strong" } },
{
$project: {
total: {
$let: {
vars: {
events: {
$filter: {
input: "$events",
cond: { $eq: ["$$this.eventType", "log"] }
}
}
},
in: {
visitor: "$$events.visitorInfo.visitorId",
uniquevisitor: {
$setUnion: "$$events.visitorInfo.visitorId"
}
}
}
}
}
}
])
Playground
Or similar approach without $let and two $project stages,
db.collection.aggregate([
{ $match: { server: "strong" } },
{
$project: {
events: {
$filter: {
input: "$events",
cond: { $eq: ["$$this.eventType", "log"] }
}
}
}
},
{
$project: {
total: {
visitor: "$events.visitorInfo.visitorId",
uniquevisitor: {
$setUnion: "$events.visitorInfo.visitorId"
}
}
}
}
])
Playground

Mongo Compaas filter within Projection

Recently I am facing a challenege while creating a query in Mongo Compass. Below is the scenario.
I have a set of documents in mongo db like below:
{
_id :1,
'people':[
{
'grade' : ['A','B'],
'stream': [ {
'stream_id: 'CSE',
'stream_name': 'COMPUTER'
},
{
'stream_id: 'ECE',
'stream_name': 'ELECTRONICS'
},
]
},
{
'grade' : ['B'],
'stream': [ {
'stream_id: 'IT',
'stream_name': 'INFORMATION_TECH'
}
]
}
]
}
I need to find the 'PEOPLE' element which has grade as 'A' and stream_name as 'CSE'. So basically I want this output:
{
_id :1,
'people':[
{
'grade' : ['A','B'],
'stream': [ {
'stream_id: 'CSE',
'stream_name': 'COMPUTER'
}
]
]}
I have tried all the $elemMatch features but it's returning the whole document not only that particular index of the array. Please if anyone is aware of mongo compass, let me know.
Mongo is fun it seems :)
You can use aggregations
$unwind to deconstruct the array
$match to get necessary documents, others will be eliminated
$filter to filter the stream, since we get all the documents that passes the condition, we need to filter the stream objects which equal to condition
$group to reconstruct the array that we already deconstructed in first step
here is the code
db.collection.aggregate([
{ $unwind: "$people" },
{
$match: {
$expr: {
$and: [
{ $in: [ "A", "$people.grade" ] },
{ $in: [ "CSE", "$people.stream.stream_id" ] }
]
}
}
},
{
$addFields: {
"people.stream": {
$filter: {
input: "$people.stream",
cond: { $eq: [ "$$this.stream_id", "CSE" ] }
}
}
}
},
{
$group: {
_id: "$_id",
people: { $push: "$people" }
}
}
])
Working Mongo playground

How to reference added field in $match?

Given this aggregation pipeline:
[
{
$addFields: {
_myVar: "x"
}
},
{
$match: {
array: "x"
}
}
]
How can the field with value x only be set once?
For example, this does not work, it times out:
[
{
$addFields: {
_myVar: "x"
}
},
{
$match: {
$expr: {
$in: [
"$_myVar", "$array"
]
}
}
}
]
The variable needs to be available throughout the pipeline, so only using the value in the $match stage as condition is not a solution.
What is the solution?
You can do something like this here i added two fields and checking if _myArray has _myVar, this is just to explain how can you check... in your case you have to replace _myArray with your actual array against which you want t to match
[{
$addFields: {
_myVar: "x",
_myArray: ['X', 'Y', 'x']
}
}, {
$addFields: {
has: {
$in: ["$_myVar", "$_myArray"]
}
}
}, {
$match: {
has: true
}
}]

Aggregate and $or operator in MongoDB

This is my mongodb query:
Booking.aggregate([
{ $match:
{ $and: [
{ $or: [
{ isDoubleRoom },
{ chosenRoom }
]},
{ month },
{ year },
] }},
{ $group: { _id: "$fullDate", count: { $sum: 1 } } }
]
In a first stage I would like to filter out by month, year and conditionally: if isDoubleRoom then filter only by double rooms, if it is not then filter by chosenRoom property. The thing is that $or does not switch between filters. Query returns not filtered (by chosen isDoubleRoom $or chosenRoom) results. The same worked when I used it with find instead of aggregate. But here I need aggregate in order to count filtered results.
You should use $cond
{
$match: {
$and: [
{
$cond: [{isDoubleRoomBool}, {isDoubleRoom} , {chosenRoom}] //
},
{ month },
{ year },
]
}
}