Arguments must be aggregate pipeline operators at Aggregate.append - mongodb

https://mongoplayground.net/p/MlQFd6J4rZc
Products.aggregate({
$match: {
$or: [
{ '_id': {$regex: queryRegex } },
{ 'name': {$regex: queryRegex } },
]
},
{
$addFields: {
favorite: {
$cond: [
{
$in: [
'xxx',
'$favoritedBy'
]
},
true,
false
]
}
}
})
Adding the $addFields portion broke the code. I am not sure why. It's working on the mongoplayground example I set up. Is there any reason for this? removing $addFields removes the error, but I should be able to create a new field, so I am confused as to what's going on.
Mongoose is version 5.11.12, so it's not caused by the version number, I think.
Error: Arguments must be aggregate pipeline operators at Aggregate.append
useNewUrlParser was set to true.

Related

How to update a property of the last object of a list in mongo

I would like to update a property of the last objet stored in a list in mongo. For performance reasons, I can not pop the object from the list, then update the property, and then put the objet back. I can not either change the code design as it does not depend on me. In brief am looking for a way to select the last element of a list.
The closest I came to get it working was to use arrayFilters that I found doing research on the subject (mongodb core ticket: https://jira.mongodb.org/browse/SERVER-27089):
db.getCollection("myCollection")
.updateOne(
{
_id: ObjectId('638f5f7fe881c670052a9d08')
},
{
$set: {"theList.$[i].propertyToUpdate": 'NewValueToAssign'}
},
{
arrayFilters: [{'i.type': 'MyTypeFilter'}]
}
)
I use a filter to only update the objets in theList that have their property type evaluated as MyTypeFilter.
What I am looking for is something like:
db.getCollection("maCollection")
.updateOne(
{
_id: ObjectId('638f5f7fe881c670052a9d08')
},
{
$set: {"theList.$[i].propertyToUpdate": 'NewValueToAssign'}
},
{
arrayFilters: [{'i.index': -1}]
}
)
I also tried using "theList.$last.propertyToUpdate" instead of "theList.$[i].propertyToUpdate" but the path is not recognized (since $last is invalid)
I could not find anything online matching my case.
Thank you for your help, have a great day
You want to be using Mongo's pipelined updates, this allows us to use aggregation operators within the update body.
You do however need to consider edge cases that the previous answer does not. (null list, empty list, and list.length == 1)
Overall it looks like so:
db.collection.update({
_id: ObjectId("638f5f7fe881c670052a9d08")
},
[
{
$set: {
list: {
$concatArrays: [
{
$cond: [
{
$gt: [
{
$size: {
$ifNull: [
"$list",
[]
]
}
},
1
]
},
{
$slice: [
"$list",
0,
{
$subtract: [
{
$size: "$list"
},
1
]
}
]
},
[]
]
},
[
{
$mergeObjects: [
{
$ifNull: [
{
$last: "$list"
},
{}
]
},
{
propertyToUpdate: "NewValueToAssign"
}
]
}
]
]
}
}
}
])
Mongo Playground
One option is to use update with pipeline:
db.collection.update(
{_id: ObjectId("638f5f7fe881c670052a9d08")},
[
{$set: {
theList: {
$concatArrays: [
{$slice: ["$theList", 0, {$subtract: [{$size: "$theList"}, 1]}]},
[{$mergeObjects: [{$last: "$theList"}, {propertyToUpdate: "NewValueToAssign"}]}]
]
}
}}
]
)
See how it works on the playground example

MongoDB, How Do I combine a find and sort with the $cond in aggregation?

I have written a find query, which works, the find query returns records where name and level exist
db.docs.find( { $and: [{name:{$exists:true}},{level:{ $exists:true}} ] },{_id:0, name:1}).sort({"name":1})
and now want to combine it with something like the code below which also works, but needs to be merged with the above to pull the correct data
db.docs.aggregate(
[
{
$project:
{
_id:0,
name: 1,
Honours:
{
$cond: { if: { $gte: [ "$level", 8 ] }, then: "True", else: "False" }
}
}
}
]
)
The find query returns records where name and level exist, but I need to enhance the result with new column called Honours, showing True of False depending on whether the level is gte (greater than or equal to 8)
So I am basically trying to combine the above find filter with the $cond function (which I found and modified example here : $cond)
I tried the below and a few other permutations to try and make find and sort with the $project and$cond aggregate, but it returned errors. I am just very new to how to construct mongodb syntax to make it all fit together. Can anyone please help?
db.docs.aggregate(
[{{ $and: [{name:{$exists:true}},{level:{ $exists:true}} ] },{_id:0, name:1}).sort({"name":1}
{
$project:
{
_id:0,
name: 1,
Honours:
{
$cond: { if: { $gte: [ "$level", 8 ] }, then: "True", else: "False" }
}
}
}
}
]
)
Try below aggregation pipeline :
db.docs.aggregate([
/** $match is used to filter docs kind of .find(), lessen the dataset size for further stages */
{
$match: {
$and: [{ name: { $exists: true } }, { level: { $exists: true } }]
}
},
/** $project works as projection - w.r.t. this projection it will lessen the each document size for further stages */
{
$project: {
_id: 0,
name: 1,
Honours: {
$cond: { if: { $gte: ["$level", 8] }, then: "True", else: "False" }
}
}
},
/** $sort should work as .sort() */
{ $sort: { name: 1 } }
]);

Find all entries where one of attributes within array is empty

I've following mongodb query:
db
.getCollection("entries")
.find({
$and: [
{
"array.attribute_1": {
$exists: true,
$not: {
$size: 0
}
}
},
{
$or: [
{ "array.attribute_2": { $exists: true, $size: 0 } },
{ "array.attribute_2": { $exists: true, $eq: {} } }
]
},
]
})
And example of my document:
{
_id: 'foo',
array: [
{attribute_1: [], attribute_2: []},
{attribute_1: ['bar'], attribute_2: []}
]
}
In my understanding my query should find all entries that have at least one element within array that has existent and not empty attribute_1 and existent empty array or empty object attribute_2. However, this query finds all entries that has all elements within array that has existent and not empty attribute_1 and existent empty array or empty object attribute_2. As such, my foo entry won't be found.
What should be the correct formula for my requirements?
$find would find the first document with the matching criteria and in your case that first document contains all the arrays. You either need to use $project with $filter or aggregation with $unwind and $match.
Something like this:
db.collection.aggregate([
{ $unwind: "$array" },
{
$match: {
$and: [
{ "array.attribute_1.0": { $exists: true }},
{
$or: [
{ "array.attribute_2.0": { $exists: false } },
{ "array.attribute_2.0": { $eq: {} } }
]
}
]
}
}
])
You can see it working here
Also since you are trying to find out if array is empty and exists at the same time using .0 with $exists is a quick and one statement way to get the same result as with both $exists and $size.

$match not filtering records satisfying a condition in a generated field after $project stage

I have a movies collection and I want to find all the movies with one-word names (eg: 'Adrift' should be returned but not 'Bird Box'). I did the following and nothing is returned upon executing the command in the Mongo shell. I saw that the output of only the '$project' stage works fine where all one-word movie titles have 'titleSize = true'. So, I think something is wrong with the way I wrote the '$match' stage. I am new to Mongo and may not have understood the concept. Any help in understanding what I am doing wrong will be greatly appreciated.
db.movies.aggregate([(
{$project:
{_id:0,
title:1,
"titleSize":{
$eq:[{$size:{$split: ["$title"," "]}},1]
}
}
},
{$match:
{"titleSize":true}
}
)])
There's a better alternative: $expr. Use it as the query with the find() method:
db.movies.find({
'$expr': {
'$eq': [
{ '$size': { '$split': ["$title", " "] } },
1
]
}
})
Or if you're still going with the current aggregate pipeline, tweak it by removing the opening and closing brackets and some extra braces, you should finally have:
db.movies.aggregate([
{ $project: {
_id: 0,
title: 1,
titleSize: {
$eq: [
{ $size: {
$split: ["$title", " "]
} },
1
]
}
} },
{ $match: { titleSize: true } }
])

Compare document array size to other document field

The document might look like:
{
_id: 'abc',
programId: 'xyz',
enrollment: 'open',
people: ['a', 'b', 'c'],
maxPeople: 5
}
I need to return all documents where enrollment is open and the length of people is less than maxPeople
I got this to work with $where:
const
exists = ['enrollment', 'maxPeople', 'people'],
query = _.reduce(exists, (existsQuery, field) => {
existsQuery[field] = {'$exists': true}; return existsQuery;
}, {});
query['$and'] = [{enrollment: 'open'}];
query['$where'] = 'this.people.length<this.maxPeople';
return db.coll.find(query, {fields: {programId: 1, maxPeople: 1, people: 1}});
But could I do this with aggregation, and why would it be better?
Also, if aggregation is better/faster, I don't understand how I could convert the above query to use aggregation. I'm stuck at:
db.coll.aggregate([
{$project: {ab: {$cmp: ['$maxPeople','$someHowComputePeopleLength']}}},
{$match: {ab:{$gt:0}}}
]);
UPDATE:
Based on #chridam answer, I was able to implement a solution like so, note the $and in the $match, for those of you that need a similar query:
return Coll.aggregate([
{
$match: {
$and: [
{"enrollment": "open"},
{"times.start.dateTime": {$gte: new Date()}}
]
}
},
{
"$redact": {
"$cond": [
{"$lt": [{"$size": "$students" }, "$maxStudents" ] },
"$$KEEP",
"$$PRUNE"
]
}
}
]);
The $redact pipeline operator in the aggregation framework should work for you in this case. This will recursively descend through the document structure and do some actions based on an evaluation of specified conditions at each level. The concept can be a bit tricky to grasp but basically the operator allows you to proccess the logical condition with the $cond operator and uses the special operations $$KEEP to "keep" the document where the logical condition is true or $$PRUNE to "remove" the document where the condition was false.
This operation is similar to having a $project pipeline that selects the fields in the collection and creates a new field that holds the result from the logical condition query and then a subsequent $match, except that $redact uses a single pipeline stage which restricts contents of the result set based on the access required to view the data and is more efficient.
To run a query on all documents where enrollment is open and the length of people is less than maxPeople, include a $redact stage as in the following::
db.coll.aggregate([
{ "$match": { "enrollment": "open" } },
{
"$redact": {
"$cond": [
{ "$lt": [ { "$size": "$people" }, "$maxPeople" ] },
"$$KEEP",
"$$PRUNE"
]
}
}
])
You can do :
1 $project that create a new field featuring the result of the comparison for the array size of people to maxPeople
1 $match that match the previous comparison result & enrollment to open
Query is :
db.coll.aggregate([{
$project: {
_id: 1,
programId: 1,
enrollment: 1,
cmp: {
$cmp: ["$maxPeople", { $size: "$people" }]
}
}
}, {
$match: {
$and: [
{ cmp: { $gt: 0 } },
{ enrollment: "open" }
]
}
}])