I have a need to use $elemMatch in an aggregation pipeline and I need to compare 2 fields of an object from a nested array of objects:
Example collection:
name: 'xxx',
steps: [
{
userId: 'abc',
senderId: 'abc'
},
...
]
What I'm trying to do is return all that have at least 1 step where userId = senderId.
I have tried the following, but I get an error that $expr isn't allowed as a child of $elemMatch:
{
$match: {
steps: {
$elemMatch: {
$expr: { $eq: ['$userId', '$senderId'] },
},
},
},
}
Thanks.
$elemMatch can only be used in projection.
You can workaround for comparing fields in the array as below:
$set - Create new field filteredCount with get the array size $size of filtered array.
$match - Get filteredCount greater than 0.
db.collection.aggregate({
$set: {
filteredCount: {
$size: {
$filter: {
input: "$steps",
cond: {
$eq: [
"$$this.userId",
"$$this.senderId"
]
}
}
}
}
}
},
{
$match: {
"filteredCount": {
$gt: 0
}
}
})
Sample Mongo Playground
Related
I have a sample mongoose object that looks like this:
{
_id: 5fa849ad4f6be0382363809c,
ratings: {
ratedPersonId: 7,
rating: 7,
timeSpent: 30,
timestamp: 78,
userThreshold: 5
}
},
it contains an _id and a list of ratings which is a subdocument with the following features.
I have created an aggregation pipeline like this:
const ratedUser = await this.ratingModel
.aggregate([
{ $project: { ratings: 1 } },
{ $unwind: '$ratings' },
{
$match: {
$and: [{ 'ratings.ratedPersonId': userId }, { 'ratings.rating': { $gte: 5 } }],
},
},
])
.exec()
This works for the first condition ratings.ratedPersonId: userId
My problem is that my second condition is the rating should be greater than or equal to the userThreshold field in the same object.
whenever I type that in the query it returns nothing
$and: [{ 'ratings.ratedPersonId': userId }, { 'ratings.rating': { $gte: 'ratings.threshold'} }],
Demo - https://mongoplayground.net/p/AQMsJGkoFcu
Use $expr to compare the fields
Read aggregation-expressions
$expr can build query expressions that compare fields from the same document in a $match stage.
If the $match stage is part of a $lookup stage, $expr can compare fields using let variables. See Specify Multiple Join Conditions with $lookup for an example.
$expr only uses indexes on the from the collection for equality matches in a $match stage.
$expr does not support multikey indexes.
db.collection.aggregate([
{
$project: {
ratings: 1
}
},
{
$unwind: "$ratings"
},
{
$match: {
$and: [
{
"ratings.ratedPersonId": 7
},
{
$expr: {
$gte: [
"$ratings.rating",
"$ratings.userThreshold"
]
}
}
],
},
},
])
I need to obtain the information contained in the array named detalleInsumos through the _id of that object.
I have tried many ways, but I still have not found the solution to my problem, since it always shows me all the objects of that document, which does not work for me.
This is the document from which I need to get that information:
{
"_id":{"$oid":"5f9041196462be3c5ca1e53d"},
"codigoFinca":"000",
"nombreFinca":"PROVINCIANA",
"fechaRegistro":"2020-10-21",
"semanaRegistro":"43",
"usuarioRegistro":"cotorreo",
"trabajadoresFinca":[
{
"porcentajeRecargo":0,
"_id":{"$oid":"5f9041196462be3c5ca1e53e"},
"udpTrabajador":[
{
"unidadesAPagar":null,
"valorUnidad":"",
"areaLaborada":"2",
"semanaNormal":null,
"semanaAtrazos":null,
"_id":{"$oid":"5f9041196462be3c5ca1e53f"},
"detalleInsumos":[
{"_id":{"$oid":"5f9041196462be3c5ca1e540"},
"codigoInsumo":"20000001",
"descripcionInsumo":"NYLON X 5 KILOS",
"cantidadAplicada":"153",
"idRDI":"426715",
"idDetalleSaldo":"24070"
}
],
"codigoLabor":"101",
"nombreLabor":"AMARRE",
"loteLaboro":"1"
}
],
"codigoTrabajador":"0000",
"nombresTrabajador":"HUMBERTO MENA MOSQUERA",
"horasJornada":"10",
"horasLaboradas":"10"
}
],
"createdAt":{"$date":"2020-10-21T14:09:29.876Z"},
"updatedAt":{"$date":"2020-10-21T15:09:51.657Z"},
"__v":0
}
And this is what I have tried from nodejs:
const consultauno = await Modelo.findOne({
'trabajadoresFinca.udpTrabajador.detalleInsumos._id': new ObjectId(idInsumo)
},
{
"trabajadoresFinca.udpTrabajador.detalleInsumos": 1
});
console.log(consultauno);
You can try,
$match your condition
$reduce to iterate loop of trabajadoresFinca array, second $reduce to iterate loop of udpTrabajador array, $filter to get matching object from detalleInsumos array,
$arrayElemAt will get first object from array when condition match
$mergeObjects will merge initialValue or reduce and matching object
const consultauno = await Modelo.aggregate([
{
$match: {
"trabajadoresFinca.udpTrabajador.detalleInsumos._id": ObjectId(idInsumo)
}
},
{
$project: {
detalleInsumos: {
$reduce: {
input: "$trabajadoresFinca",
initialValue: {},
in: {
$mergeObjects: [
"$$value",
{
$reduce: {
input: "$$this.udpTrabajador",
initialValue: {},
in: {
$mergeObjects: [
"$$value",
{
$arrayElemAt: [
{
$filter: {
input: "$$this.detalleInsumos",
cond: {
$eq: ["$$this._id", ObjectId(idInsumo)]
}
}
},
0
]
}
]
}
}
}
]
}
}
}
}
}
])
Playground
Second approach, you can use $unwind,
$match your conditions
$unwind deconstruct trabajadoresFinca array
$unwind deconstruct udpTrabajador array
$unwind deconstruct detalleInsumos array
$match your conditions
$project to show required fields
const consultauno = await Modelo.aggregate([
{
$match: {
"trabajadoresFinca.udpTrabajador.detalleInsumos._id": ObjectId(idInsumo)
}
},
{ $unwind: "$trabajadoresFinca" },
{ $unwind: "$trabajadoresFinca.udpTrabajador" },
{ $unwind: "$trabajadoresFinca.udpTrabajador.detalleInsumos" },
{
$match: {
"trabajadoresFinca.udpTrabajador.detalleInsumos._id": ObjectId(idInsumo)
}
},
{
$project: {
trabajadoresFinca: "$trabajadoresFinca._id",
udpTrabajador: "$trabajadoresFinca.udpTrabajador._id",
detalleInsumos: "$trabajadoresFinca.udpTrabajador.detalleInsumos"
}
}
])
Playground
I need to get docs from MongoDB collection where ID's are in array:
[
'5f80a44d0179262f7c2e6a42',
'5f8c00762fae890e9c4d029c',
'5f802cf8abac1116a46bf9d4'
]
The issue is, docs are not coming in sequence of my array ID's. They are coming (1, 0, 2) for above array ID's.
How can I make them in sequence of my ID's array? I am using, NodeJs + Mongoose.
My code:
var ids = ['5f80a44d0179262f7c2e6a42','5f8c00762fae890e9c4d029c','5f802cf8abac1116a46bf9d4']
Product.find({
_id: {
$in: ids
}
})
I don't think its possible with find(), or any functionality available in MongoDB related to this,
It is possible with aggregate() but this will just fulfil your expected result,
I am not recommending to use this because this will affect performance of query response, this could be a heavy transaction.
$match your conditions
$group by null and make array of all matching objects in root
$addFields to add ids array that we search for
$unwind deconstruct ids array, this will deconstruct in sequence as per we provided in ids array
$project, $reduce input as root array and check condition if id match then return object
$replaceWith to replace root object to root
var ids = [
ObjectId("5f802cf8abac1116a46bf9d4"),
ObjectId("5f8c00762fae890e9c4d029c"),
ObjectId("5f80a44d0179262f7c2e6a42")
];
Product.aggregate([
{ $match: { _id: { $in: ids } } },
{
$group: {
_id: null,
root: { $push: "$$ROOT" }
}
},
{ $addFields: { ids: ids } },
{ $unwind: "$ids" },
{
$project: {
root: {
$reduce: {
input: "$root",
initialValue: {},
in: { $cond: [{ $eq: ["$$this._id", "$ids"] }, "$$this", "$$value"] }
}
}
}
},
{ $replaceWith: "$root" }
])
Playground
I'm trying to query my database to get a specific element from an array, then only project a part of that array, here is the code I tried:
{ $project : {
name: 1,
language : 1 ,
season: [{
$filter: {
input: "$seasons",
as: "s",
cond: { $eq: [ "$$s.number", saison ] }
}
}, {
$project: {
'episodes.number': 1
}
}]
} }
I want to only get the season that matches the number, then project the number field only.
Here is my schema:
{
name: {type: String},
seasons: [{
number: Number,
episodes: [{number: Number, videos: [
{
provider: String,
quality: String,
language: String,
added: { type: Date, default: new Date(1510272000000) }
}
]}]
}]
}
My current query is generating an error: MongoError: Unrecognized expression '$project', if I do the filter without the $project after it works, but then it returns a whole array instead of what I just need. Thank you.
You have a mistake in your aggregation pipeline. What you mean to say is this?
db.collectionName.aggregate([
{
$project: {
name: 1,
language: 1,
season: {
$filter: {
input: "$seasons",
as: "s",
cond: {
$eq: ["$$s.number", saison]
}
}
}
}
},
{
$project: {
'season.episodes.number': 1
}
}
])
and if you want to return only a single number without the complete array structure:
db.collectionName.aggregate([
{
$project: {
name: 1,
language: 1,
season: {
$filter: {
input: "$seasons",
as: "s",
cond: {
$eq: ["$$s.number", 1]
}
}
}
}
},
{ $unwind: "$season"},
{ $unwind: "$season.episodes"},
{
$project: {
seasonEpisodeNumber: '$season.episodes.number'
}
}
])
Unrecognized expression '$project'
Your second $project was inside your first $project that's why the error message. Each pipeline is one execution, so you can't have nested pipelines. If you need to have two projects back to back then you do it like that example:
{ $project: {...}}, { $project: {...}}
and the field number is inside the array episodes which is in the object field seasons, so you were missing that.
Also in your $filter there is no need to create a double array. You do this:
season: [{
$filter: {
input: "$seasons",
as: "s",
cond: { $eq: [ "$$s.number", saison ] }
} etc...
that creates an array inside an array. Unless you expect the result in that form, there is no reason to do that. In my answer I removed the nested array creation.
I have a collection with name post and I have one doc and its replicated doc but in replicated doc we have one field different ,some doc don't have replicated doc and that situation depends on an array field of the doc ,if that field have the userId of user then the replicated doc will exist otherwise it will not exist.
So what I want if the doc array have that id then get the replicated post and if not then original post
I have made the query but showing error I am using $exist in $cond ?
Post.aggregate([
{
$match: {
socomo_visibility: socomoId
}
},
{
$project: {
"post_stream_type": {
$cond: {
if: {
'following_users_list': {
$exist: [userId]
}
},
then: constants.POST_STREAM_TYPE.FOLLOW.value,
else: constants.POST_STREAM_TYPE.SOCIAL_CURRY_CHANNEL.value
}
}
}
}
]
You can check whether your array has some value in the boolean-expression in such way:
Do intersection of array and value using $setIntersection.
Check size of that intersection array using $size.
If the size is greater than 0 then value is present in the array. $gt will do this check.
Try the following code:
Post.aggregate([
{
$project: {
"post_stream_type": {
$cond: {
if: {$gt: [{$size: {$setIntersection: ["$following_users_list", [userId]] } }, 0] },
then: constants.POST_STREAM_TYPE.FOLLOW.value,
else: constants.POST_STREAM_TYPE.SOCIAL_CURRY_CHANNEL.value
}
}
}
}
])
okay finally I have done this without using the aggregation .
my answer for the query is
Post.find({
$or: [{
socomo_visibility: {
$elemMatch: {
$eq: socomoId
}
},
post_stream_type: constants.POST_STREAM_TYPE.SOCIAL_CURRY_CHANNEL.value,
following_users_list: {
$exists: true,
$nin: [userId]
}
},
{
socomo_visibility: {
$elemMatch: {
$eq: socomoId
}
},
post_stream_type: constants.POST_STREAM_TYPE.FOLLOW.value,
following_users_list: {
$elemMatch: {
$eq: userId
}
}
}]
})