Mongoose: MongoError: >1 field while trying to project out $elemMatch - mongodb

I'm trying to project out only the matched element of an array, in the updated version. But I'm getting the error: "MongoError: >1 field in obj: { _id: 0, lotes.$: 1 }"
If I remove 'new: true', it works. But then I have the doc before the update. And I would really like the updated version.
What's wrong? How can I fix it?
The Offer doc is something like:
{
_id
series: [ Serie ]
}
Serie structure is something like:
{
_id
public.available: Number
public.expDate: Date
}
I'm using Mongoose:
var query = {
'_id': offerId,
'series': {
$elemMatch: {
'_id': serieId,
'public.available': {$gt:0},
'public.expDate': {$gt: now}
}
}
};
var update = {
$inc: { 'series.$.public.available' : -1 }
};
var options = { // project out just the element found, updated
new:true,
select: {
'_id': 0,
'series.$': 1
}
};
Offers.findOneAndUpdate(query, update, options)
.then( element => {
...
}

For anyone else experiencing this error, it is also the most common error when trying to perform an illegal action such as trying to update a database element inside of a findOne request.
Making sure your request is correct, such as findOneAndUpdate should be your first port of call when you get this error.

As Anthony Winzlet pointed out in the links, there seems to be an issue with Mongoose, in which if you use 'new:true', you can't project out the $elemMatch.
So my solution was to keep using 'new:true' only, without projections. And reduce the array later on to get the $elemMatch:
.then( (result) => {
var aux = result.series.reduce((acu, serie, index) => {
if (serie._id == req.params.serieId) return index;
});
var element = result.series[aux];
}

Related

How can I return the element I'm looking for inside a nested array?

I have a database like this:
[
{
"universe":"comics",
"saga":[
{
"name":"x-men",
"characters":[
{
"character":"wolverine",
"picture":"618035022351.png"
},
{
"character":"cyclops",
"picture":"618035022352.png"
}
]
}
]
},
{
"universe":"dc",
"saga":[
{
"name":"spiderman",
"characters":[
{
"character":"venom",
"picture":"618035022353.png"
}
]
}
]
}
]
and with this code I manage to update one of the objects in my array. specifically the object where character: wolverine
db.mydb.findOneAndUpdate({
"universe": "comics",
"saga.name": "x-men",
"saga.characters.character": "wolverine"
}, {
$set: {
"saga.$[].characters.$[].character": "lobezno",
"saga.$[].characters.$[].picture": "618035022354.png",
}
}, {
new: false
}
)
it returns all my document, I need ONLY the document matched
I would like to return the object that I have updated without having to make more queries to the database.
Note
I have been told that my code does not work well as it should, apparently my query to update this bad, I would like to know how to fix it and get the object that matches these search criteria.
In other words how can I get this output:
{
"character":"wolverine",
"picture":"618035022351.png"
}
in a single query using filters
{
"universe": "comics",
"saga.name": "x-men",
"saga.characters.character": "wolverine"
}
My MongoDB knowledge prevents me from correcting this.
Use the shell method findAndModify to suit your needs.
But you cannot use the positional character $ more than once while projecting in MongoDb, so you may have to keep track of it yourself at client-side.
Use arrayFilters to update deeply nested sub-document, instead of positional all operator $[].
Below is a working query -
var query = {
universe: 'comics'
};
var update = {
$set: {
'saga.$[outer].characters.$[inner].character': 'lobezno',
'saga.$[outer].characters.$[inner].picture': '618035022354.png',
}
};
var fields = {
'saga.characters': 1
};
var updateFilter = {
arrayFilters: [
{
'outer.name': 'x-men'
},
{
'inner.character': 'wolverine'
}
]
};
db.collection.findAndModify({
query,
update,
fields,
arrayFilters: updateFilter.arrayFilters
new: true
});
If I understand your question correctly, your updating is working as expected and your issue is that it returns the whole document and you don't want to query the database to just to return these two fields.
Why don't you just extract the fields from the document returned from your update? You are not going to the database when doing that.
var extractElementFromResult = null;
if(result != null) {
extractElementFromResult = result.saga
.filter(item => item.name == "x-men")[0]
.characters
.filter(item => item.character == "wolverine")[0];
}

MongoDB updating the wrong subdocument in array

I've recently started using MongoDB using Mongoose (from NodeJS), but now I got stuck updating a subdocument in an array.
Let me show you...
I've set up my Restaurant in MongoDB like so:
_id: ObjectId("5edaaed8d8609c2c47fd6582")
name: "Some name"
tables: Array
0: Object
id: ObjectId("5ee277bab0df345e54614b60")
status: "AVAILABLE"
1: Object
id: ObjectId("5ee277bab0df345e54614b61")
status: "AVAILABLE"
As you can see a restaurant can have multiple tables, obviously.
Now I would like to update the status of a table for which I know the _id. I also know the _id of the restaurant that has the table.
But....I only want to update the status if we have the corresponding tableId and this table has the status 'AVAILABLE'.
My update statement:
const result = await Restaurant.updateOne(
{
_id: ObjectId("5edaaed8d8609c2c47fd6582"),
'tables._id': ObjectId("5ee277bab0df345e54614b61"),
'tables.status': 'AVAILABLE'
},
{ $set: { 'tables.$.status': 'CONFIRMED' } }
);
Guess what happens when I run the update-statement above?
It strangely updates the FIRST table (with the wrong table._id)!
However, when I remove the 'tables.status' filter from the query, it does update the right table:
const result = await Restaurant.updateOne(
{
_id: ObjectId("5edaaed8d8609c2c47fd6582"),
'tables._id': ObjectId("5ee277bab0df345e54614b61")
},
{ $set: { 'tables.$.status': 'CONFIRMED' } }
);
Problem here is that I need the status to be 'AVAILABLE', or else it should not update!
Can anybody point me in the wright direction with this?
according to the docs, the positional $ operator acts as a placeholder for the first element that matches the query document
so you are updating only the first array element in the document that matches your query
you should use the filtered positional operator $[identifier]
so your query will be something like that
const result = await Restaurant.updateOne(
{
_id: ObjectId("5edaaed8d8609c2c47fd6582"),
'tables._id': ObjectId("5ee277bab0df345e54614b61"),
'tables.status': 'AVAILABLE'
},
{
$set: { 'tables.$[table].status': 'CONFIRMED' } // update part
},
{
arrayFilters: [{ "table._id": ObjectId("5ee277bab0df345e54614b61"), 'table.status': 'AVAILABLE' }] // options part
}
);
by this way, you're updating the table element that has that tableId and status
hope it helps

Mongoose remove one object from array of array

I have Mongoose schema like this:
{
......
project: [
{
Name: String,
Criteria:[
{
criteriaName:String,
}
]
}
]
......
}
And I want to remove one of the objects of criteria array which is in the array of project based on the object id
I tried the code following
criteria.findOneAndUpdate({
"_id": uid,
},{ $pull: { "project.Criteria": { _id: cid } } }, (err) => {
......
}
However this cannot work, it said "Cannot use the part (Criteria) of (project.Criteria) to traverse the element"
Do you need to do it in one query to the database? If not, the following solution may work for you:
criteria.findOne({ _id: uid })
.then((obj) => {
// Filter out the criteria you wanted to remove
obj.project.Criteria = obj.project.Criteria.filter(c => c._id !== cid);
// Save the updated object to the database
return obj.save();
})
.then((updatedObj) => {
// This is the updated object
})
.catch((err) => {
// Handle error
});
Sorry if the .then/.catch is confusing. I can rewrite with callbacks if necessary, but I think this looks a lot cleaner. Hope this helps!

Trouble updating a Simple Schema sub document

I'm trying to update a sub document on an existing collection. I'm getting a MongoDB error message.
"MongoError: The positional operator did not find the match needed from the query. Unexpanded update: articleWords.$ [409]"
From my Articles Simple Schema
"articleWords.$": {
type: Object
},
"articleWords.$.wordId": {
type: String,
label: 'Word ID'
},
"articleWords.$.word": {
type: String,
label: 'Word'
},
Update Function
function updateArticle(_id,wordArr) {
_.each(wordArr,function(elem) {
var ret = Articles.update(
{'_id': _id},
{ $set: { 'articleWords.$': { 'wordId': elem.wordId, 'word': elem.word } }
});
});
return true;
}
As you can see I am passing an array of objects. Is there a better way to do this than _.each ?
CLARIFICATION
Thank you to #corvid for the answer. I think I didn't make my question clear enough. There does exist an article record, but there is no data added to the articleWords attribute. Essentially we are updating a record but insert into the articleWords array.
A second attempt, is also not working
_.each(wordArr,function(elem) {
var ret = Articles.update(
{'_id': _id},
{ $set: { 'articleWords.$.wordId': elem.wordId, 'articleWords.$.word': elem.word } }
);
});
Yes, you need your selector to match something within the subdocument. For example,
Articles.update({
'_id': <someid>,
'words.wordId': <somewordid>
}, {
$set: {
'words.$.word': elem.word,
'words.$.wordId': elem.wordId
}
});
If the array doesn't exist yet then you're going about this in the hardest way possible. You can just set the entire array at one go:
var ret = Articles.update(
{'_id': _id},
{ $set: { articleWords: wordArr }}
);
I can see that wordArr already has the id and string. This will work as long as it doesn't have more content. If it does then you can just make a second version with the parts you want to keep.

findAndModify query not executing in callback to aggregation

I have an aggregation query on a students collection that is returning two sets of results
for each student like this
{ _id: 1543,
name: 'Bill Jackson',
scores: { type: 'homework', score: 38.86823689842918 } }
{ _id: 1543,
name: 'Bill Jackson',
scores: { type: 'homework', score: 15.861613903793295 } }
That's working fine. Now in the callback I want to remove one of the scores for each student. I use ugly nested conditionals below to isolate which of the two records I want to remove, and, once that's achieved I create a find and Modify query to remove the doc but there's no evidence of it getting run. Neither the error or success callback to the findAndModify are getting run, however I am able to log that I'm inside the area where the findAndModify is getting called.
Is it possible to query the db in the callback to an aggregation? If not, how should I perform an operation that persists in the db?
//aggregation query ommitted
, function(err, result) { //callbackstarts here with result of aggregation query that returns two records for each student
for (var i=0; i<result.length; i++) {
var id = result[i]['_id'];
if (id === result[i]['_id']){
if (foo && foo === result[i]['_id']){
//if we're in here, we know we need to remove score associated with this result[i]['_id']
//create findAndModify to remove the record
var query = { '_id' : result[i]['_id']}
var sort = []
var operation = { '$pull' : { 'scores.score' : result[i]['scores']['score'] } };
var options = []
console.log('this code is getting called but findAndModify not')
db.collection('students').findAndModify(query, sort, operation, options,function(err, doc) {
if(err) throw err;
if (!doc) {
console.log("record not found");
}
else {
console.log("changed doc" + doc);
}
});
}else {
var foo = result[i]['_id'] //part of logic to isolate which of two records to remove
}