mongoDB: update nested array element - mongodb

I have the following datastructure
{
_id: ObjectId('61ae12bfb8047effd0ac2a01'),
data: [
{
xml: {
messageId: 1638798015073,
xmlString: 'someXML'
},
data: [
{
customerId: 123456,
validation: {
isValid: true,
message: ''
},
docs: [
{
objectId: 'PA1106:zt:bb302216879669b58c141b12dcdd5eb0',
writtenBack: false
}
]
},
{
customerId: 55555,
validation: {
isValid: true,
message: ''
},
docs: [
{
objectId: 'PA1106:zt:bb302216879669b58b143ef38c016217',
writtenBack: true
}
]
}
]
},
{
xml: {
messageId: 1638798015094,
xmlString: 'someXML'
},
data: [
{
customerId: 55555,
validation: {
isValid: true,
message: ''
},
docs: [
{
objectId: 'PA1106:zt:bb302216879669b58c1416129062c2d2',
writtenBack: false
},
{
objectId: 'PA1106:zt:b8be9ea04011c2a18c148a0d4c9d6aab',
writtenBack: true
}
]
},
]
},
],
createdAt: '2021-12-06T13:40:15.096Z',
createdBy: 'Test'
}
Now I want to update the writtenBack property of for a given Document and objectId. How would I write a query for this?
my updateOne looked like this
{
_id: '61ae12bfb8047effd0ac2a01',
'data.data.docs.objectId': 'PA1106:zt:bb302216879669b58b143ef38c016217'
},
{
$set: { 'data.data.docs.$.writtenBack': true }
}
I know there is arrayFilters for nested arrays, but as far as I know, I need a unique identifier for each array-Level. But I only have the objectId which is unique for a Document. Any Ideas?

Yes, you can achieve the update for the document in the nested array with positional operator $[] and arrayFilters.
db.collection.update({
_id: "61ae12bfb8047effd0ac2a01",
},
{
$set: {
"data.$.data.docs.$[doc].writtenBack": true
}
},
{
arrayFilters: [
{
"doc.objectId": "PA1106:zt:bb302216879669b58b143ef38c016217"
}
]
})
Sample Mongo Playground

I made it work with this query
{
_id: '61ae12bfb8047effd0ac2a01',
},
{
$set: { 'data.$[elem1].data.$[elem2].docs.$[elem3].writtenBack': true }
},
{
arrayFilters: [
{ 'elem1.xml.messageId': 1638798015094 },
{ 'elem2.customerId': 55555},
{ 'elem3.objectId': 'PA1106:zt:bb302216879669b58c1416129062c2d2'}]
}

Related

nested condition in mongodb aggregation

I have a collection product which looks like this.
{
should_show: {
type: String,
enum: ['always', 'never', 'on_date'],
default: 'always',
},
show_start_date: { type: Date },
show_end_date: { type: Date },
categories: [{ type: ObjectId }],
price: number,
}
find condition like this works fine when I find many products.
const condition = {
'$and': [
{ '$and': [ { categories: '61bdd930fb1dfb1f65a21f13' } ] },
{
'$or': [
{ should_show: 'always' },
{
should_show: 'on_date',
show_start_date: { '$lt': 2022-01-03T08:56:19.589Z },
show_end_date: { '$gt': 2022-01-03T08:56:19.589Z }
}
]
}
]
}
Products.find(condition)
But I changed my business logic to use Model.aggregate() instead of `Model.find()
(because I have to use allowdiskuse option)
const condition = {
'$and': [
{ '$and': [ { categories: '61bdd930fb1dfb1f65a21f13' } ] },
{
'$or': [
{ should_show: 'always' },
{
should_show: 'on_date',
show_start_date: { '$lt': 2022-01-03T08:56:19.589Z },
show_end_date: { '$gt': 2022-01-03T08:56:19.589Z }
}
]
}
]
}
Products.aggregate([
{ $match: condition },
])
And returned results is always empty array []
I changed my condition simpler like this
const condition = {
'$and': [ { categories: '61bdd930fb1dfb1f65a21f13' } ],
'$or': [
{ should_show: 'always' },
{
should_show: 'on_date',
show_start_date: { '$lt': 2022-01-03T08:56:50.161Z },
show_end_date: { '$gt': 2022-01-03T08:56:50.161Z }
}
]
}
Products.aggregate([
{ $match: condition },
])
but still returns empty array.
How should I fix this problem?
Self Answer:
I have to pass ObjectId by explicit ObjectId type, not string.
const condition = {
'$and': [
{ '$and': [{
// like this:
categories: Mongoose.Types.ObjectId('61bdd930fb1dfb1f65a21f13'),
}] },
{
'$or': [
{ should_show: 'always' },
{
should_show: 'on_date',
show_start_date: { '$lt': 2022-01-03T08:56:19.589Z },
show_end_date: { '$gt': 2022-01-03T08:56:19.589Z }
}
]
}
]
}
Products.aggregate([
{ $match: condition },
])

How to Combine GeoQuery with another separate query in MongoDB and paginate result?

let params = {
$or: [
{//STATUS1 PERSON
status: { '$in': ['Status1'] }, object: { '$in': ['Person'] },
geometry: {
$near: {
$maxDistance: 160000,
$geometry: {
type: "Point",
coordinates: [geoCoordinates[1], geoCoordinates[0]]
}
}
},
name: { '$in': req.user.names },
sessionStartTime: { "$gte": new Date() },
$and: [
{
user_id: '',
},
{
userID: { $exists: false }
}
]
},
{ //STATUS1 ALL OTHER ITEM TYPES
status: { '$in': ['Status1'] }, object: { '$in': ['Animal', 'Planet', 'Star'] },
name: { '$in': req.user.names },
sessionStartTime: { "$gte": new Date() },
$and: [
{
user_id: '',
},
{
userID: { $exists: false }
}
]
},
{ //ALL ITEM TYPES WITH STATUS2 AND STATUS3
status: { '$in': ['Status2', 'Status3'] }, object: { '$in': ['Person', 'Animal', 'Planet', 'Star'] },
user_id: req.user._id
}
]
}
var options = {
page: 1,
limit: 5
};
let foundItems = await User.paginate(params, options).then(function (result) {
});
With this query, I'm getting the following error:
MongoError: geoNear must be top-level expr
How do I include geometry query as one query among others and return the combined result with pagination ? Or is there a different way to achieve the same result?

mongodb - how to project last element of an array after an update

So I have the following query:
ListModel
.findOneAndUpdate(
{
_id: list_id
},
{
$push: {
"items": {
details: the_template
}
}
},
{
new: true,
projection: {
items: 1
}
}
)
So, this returns me the array of the updated document, but how would I get only the last element inserted?
You can use of $slice, in your case :
ListModel
.findOneAndUpdate(
{
_id: list_id,
},
{
$push: {
items: {
details: the_template,
},
},
},
{
new: true,
projection: {
items: {
$slice: -1,
},
},
},
);

MongoDB $pull from array with filter

I have User collection:
[
{
_id: '5b3935c2d4850aa2d9f0ae25',
feedBucket: [
{
_id: '2a3535c2d4852ba2d9f0ae52',
createdAt: '2018-06-30 21:52:22.681'
},
{
_id: '9f342c2d4852ba2d9f0ae52',
createdAt: '2018-06-30 21:52:22.681'
}
]
},
{
_id: '3d2343c2d4850aa2d9f0ae33',
feedBucket: [
{
_id: '2a3535c2d4852ba2d9f0ae52',
createdAt: '2018-02-30 21:52:22.681'
},
{
_id: '9f342c2d4852ba2d9f0ae52',
createdAt: '2018-06-30 21:52:22.681'
}
]
}
]
And I am trying to pull document with id - '9f342c2d4852ba2d9f0ae52' from users feedBucket.
await User.updateMany(
{},
{ $pull: { feedBucket: { _id: '9f342c2d4852ba2d9f0ae52' } } },
{ multi: true }
)
but it doesn't work, result is { n: 2, nModified: 0, ok: 1 }
If your feedBucket._id is ObjectId then you need to $pull it by converting to mongoose ObjectId
const user = await User.update(
{ },
{ $pull: { feedBucket: { _id: mongoose.Types.ObjectId('5b07fa836569d356ba5bd2fa') } } },
{ multi: true }
)

using update with upsert and $all

I cannot manage to get this to work:
db.library.update({
categories: {
$all: ['/movie/action', '/movie/comedy'],
$nin: ['/movie/cartoon']
},
location: {
$geoWithin: {
$centerSphere: [[48.8574946, 2.3476296000000048], 50/6378.1]
}
}
},
{
$setOnInsert: {
categories: ['/movie/action', '/movie/comedy'],
location: {
type: 'Point',
coordinates: [48.8574946, 2.3476296000000048]
}
},
$addToSet: {users: {_id: '1', date: '2018-04-06'}}
},
{ upsert: true })
It returns the following error:
cannot infer query fields to set, path 'categories' is matched twice
I understand that query part is moved to update part when upsert happens, but I'm not sure how to keep $all from having this effect
It does work when $all array is not set to more than 1 element.
I've found this solution, even though it's painful to be forced to list $all elements under { $elemMatch: { $eq:... :
db.library.update({
categories: {
$all: [
{ $elemMatch: { $eq: '/movie/action' } },
{ $elemMatch: { $eq: '/movie/comedy' } }
],
$nin: ['/movie/cartoon']
},
location: {
$geoWithin: {
$centerSphere: [[48.8574946, 2.3476296000000048], 50/6378.1]
}
}
},
{
$setOnInsert: {
categories: ['/movie/action', '/movie/comedy'],
location: {
type: 'Point',
coordinates: [48.8574946, 2.3476296000000048]
}
},
$addToSet: {users: {_id: '1', date: '2018-04-06'}}
},
{ upsert: true })
any simpler solution is welcome