$unset not working on array of objects? - mongodb

I have a collection with documents like below:
{
title: "whatever",
answers: [
{id: 10, question: ObjectId("54380350a52147000aad4e9b")},
{id: 13, question: ObjectId("54380350a52147000aad4e9c")},
{id: 33}
]
}
I'm have attempted to unset the question attribute using the commands below:
db.participants.update({}, {$unset: {"answers.question": ""}}, {upsert: false, multi:true} )
and
db.participants.update({}, {$unset: {"answers.question": 1}}, {upsert: false, multi:true} )
Both of these spit out: WriteResult({ "nMatched" : 628795, "nUpserted" : 0, "nModified" : 0 }) when completed, but no documents are updated (as the message suggests). Any idea why my update isn't working?

Your update is not working because answers is an array. So you should use the positional operator:
db.participants.update({ 'answers.question': { $exists: true } }, { $unset: { 'answers.$.question': '' } }, { upsert: false, multi: true });
However it updates only one item in the array. This is a limitation of MongoDB, see SERVER-1243.
You can use a forEach to iterate through the items of the array:
db.eval(function () {
db.participants.find({ 'answers.question': { $exists: true } }).forEach(function (participant) {
participant.answers.forEach(function (answer) {
delete answer.question;
});
db.participants.update({ _id: participant._id }, { $set: { answers: participant.answers } });
});
});

Related

in mongodb, how to create a unique index for a list of documents?

I have an array of documents like this:
[
{
_id: ObjectId("63845afd1f4ec22ab0d11db9"),
ticker: 'ABCD',
aggregates: [
{ date: '2022-05-20' },
{ date: '2022-05-20' },
{ date: '2022-05-20' }
]
}
]
How may I create an unique index on aggregates.date, so user may not push a duplicate date into array aggregates.
My existing aggregates are as follows:
db.aggregates_1_day.getIndexes()
[
{ v: 2, key: { _id: 1 }, name: '_id_' },
{ v: 2, key: { ticker: 1 }, name: 'ticker_1', unique: true },
{
v: 2,
key: { 'aggregates.date': 1 },
name: 'aggregates.date_1',
unique: true
}
]
Unique index ensure no duplicates across documents , but do not enforce uniqness for objects in array in same collection document.
But you have few other options here:
1. Do not use $push , but use $addToSet instead to add unique objects inside aggregates array of objects:
db.collection.update({},
{
"$addToSet": {
"aggregates": {
date: "2022-05-20"
}
}
})
note: $addToSet
only ensures that there are no duplicate items added to the set and does not affect existing duplicate elements.
Playground
2. You can configure schema validation:
> db.runCommand({collMod:"aggregates_1_day", validator: {$expr:{$eq:[{$size:"$aggregates.date"},{$size:{$setUnion:"$aggregates.date"}}]}}})
> db.aggregates_1_day.insert({aggregates:[{date:1}]}) /* success */
> db.aggregates_1_day.update({},{ '$push' : { 'aggregates':{date:1}}})
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 121,
"errmsg" : "Document failed validation"
}
})
>
more details in the mongoDB ticket
Note: In this approach you will need to clean the duplicates in advance otherways the validation will not allow to $push new objects at all.
In case you dont like it you can remove validation with:
db.runCommand({
collMod: "aggregates_1_day",
validator: {},
validationLevel: "off"
})
3. You can use update/aggregation as follow:
db.collection.update({},
[
{
$set: {
aggregates: {
$cond: [
{
$in: [
"2022-02-02",
"$aggregates.date"
]
},
"$aggregates",
{
$concatArrays: [
"$aggregates",
[
{
date: "2022-02-02"
}
]
]
}
]
}
}
}
])
Explained:
Add the object to the array only if do not exist in the array of objects.
Playground3

How to get updated document in mongodb

When I updated document and I need a updated document
const updateMe = await Friends.updateOne(
{
userId: req.user._id,
"friends._id": req.params.id,
},
{
$set: { "friends.$.status": "added" },
},
{ returnDocument: "after" }
);
console.log(updateMe);
But it s returning that
{
acknowledged: true,
modifiedCount: 0,
upsertedId: null,
upsertedCount: 0,
matchedCount: 0
}
How can I get it?
I recommend that you use findOneAndUpdate because it has the option to get the updated document.
Example from mongo docs
db.students2.findOneAndUpdate(
{ _id : 1 },
[ { $set: { "total" : { $sum: "$grades.grade" } } } ], // The $set stage is
an alias for ``$addFields`` stage
{ returnNewDocument: true }
)
MongoDb findOneAndUpdate Documentation

Bulk insert.update in mongodb

Currently in my MongoDB collection I have this data
currentDataList = [{'id':1,'name':test1},{'id':2,'name':test2}]
but now I have a new data list with huge amount of data like
newDataList = [{'id':1,'name':a1},{'id':2,'name':a2},{'id':3,'name':a3}.....{'id':9999,'name':a9999}]
and I need to update this newDataList in my collection with insert if not exist/update if exist manner.
I do not want to update the documents one by one because it will take time.
Can anyone please suggest some solution for bulk insert/update in this case.
Update in bulk:
If you want to update in bulk you can use db.runCommand() with update command.
Command example:
db.runCommand({
update: "collectionName",
updates: [
{
q: { id: 3 },
u: { $set: { id: 3, name: "a3", newFields: "newField" } },
multi: true,
upsert: true
}, // the list goes here
{
q: { id: 11 },
u: { $set: { id: 11, name: "a11" } },
multi: true,
upsert: true
}
],
ordered: false,
writeConcern: { w: "majority", wtimeout: 5000 }
});
Update one by one:
Alternately you can run the query for each item in the list using the .update() method with upsert set to true.
mongodb playground example
operation example:
db.collection.update({
id: 3
},
{
$set: {
id: 3,
name: "a3",
newField: "newField",
}
},
{
upsert: true,
multi: true
})

Mongoose / MongoDB - How to push new array element onto correct parent array element

mongodb 3.0.7
mongoose 4.1.12
I want to push new element : "bbb"
onto groups array which lives inside outer orgs array ...
original mongo data from this :
{
orgs: [
{
org: {
_bsontype: "ObjectID",
id: "123456789012"
},
groups: [
"aaa"
]
}
],
_id: {
_bsontype: "ObjectID",
id: "888888888888"
}
}
to this :
{
orgs: [
{
org: {
_bsontype: "ObjectID",
id: "123456789012"
},
groups: [
"aaa",
"bbb"
]
}
],
_id: {
_bsontype: "ObjectID",
id: "888888888888"
}
}
Here is a hardcoded solution yet I do not want
to hardcode array index (see the 0 in : 'orgs.0.groups' )
dbModel.findByIdAndUpdate(
{ _id: ObjectId("888888888888".toHex()), 'orgs.org' : ObjectId("123456789012".toHex()) },
{ $push : { 'orgs.0.groups' : 'bbb'
}
},
{ safe: true,
upsert: false,
new : true
}
)
... I was hoping a simple 'orgs.$.groups' would work, but no. Have also tried 'orgs.groups' , also no.
Do I really need to first retrieve the orgs array, identify the index
then perform some second operation to push onto proper orgs array element ?
PS - suggested duplicate answer does not address this question
Found solution, had to use
dbModel.update
not
dbModel.findOneAndUpdate nor dbModel.findByIdAndUpdate
when using '$' to indicate matched array index in multi-level documents
'orgs.$.groups'
this code works :
dbModel.update(
{ _id: ObjectId("888888888888".toHex()), 'orgs.org' : ObjectId("123456789012".toHex()) },
{ $push : { 'orgs.$.groups' : 'bbb'
}
},
{ safe: true,
upsert: false,
new : true
}
)
I wonder if this is a bug in mongoose ? Seems strange findOneAndUpdate fails to work.

How do i make logical operation $or on array field in mongo db

In a MongoDB collection, I have some data after an aggregation:
{
"_id" : ObjectId("5411e00c0d42e2a3688b47d3"),
"found" : [false, false, false, false],
}
How can I do the logical OR on array "found", to get true if any of elements is set to true, оf false if all elements are false?
I'm trying this, but it always returns true:
db.test.aggregate( {
$project:
{
found: { $or: "$found" }
}
});
// "result" : [
// {
// "_id" : ObjectId("5411e00c0d42e2a3688b47d3"),
// "found" : true
// }
// ],
P.S. I am using MongoDB 2.6
Starting from version 2.6 you can use $anyElementTrue:
db.test.aggregate({
$project: {
like: { $anyElementTrue: ["$found"] }
}
});
See this:
http://docs.mongodb.org/manual/reference/operator/aggregation/anyElementTrue/#exp._S_anyElementTrue
If you use earlier version then I'm afraid that you'll have to use either map/reduce or $unwind -> $group combination. Something like that:
db.test.aggregate([
{
$unwind: "found"
},
{
$group: {
_id: "_id",
like: { $or: "found" }
}
}
]);