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

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.

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

Append an object to an array inside a nested object

I have a collection in MongoDB in which one of the documents looks like this:
{
_id: ObjectId("6162883719592ea3350d3c87"),
fullName: 'Random User',
username: 'ruser1',
password: 'asdadasd',
portfolio: [ { equity: [] }, { crypto: [] }, { etf: [] }, { cash: [] } ]
}
I am trying to append a new object of the following format to the equity array inside the portfolio.
Object format:
{
name : "AAPL",
quantity : 1,
price : 100
}
I was trying to use the $push to do this operation, but I'm encountering the following error:
db.users.updateOne(
{_id : ObjectId("6162883719592ea3350d3c87")},
{$push : {"portfolio.equity" : {
name : "AAPL",
quantity : 1,
price : 100
}
}
}
)
MongoServerError: Cannot create field 'equity' in element {portfolio: [ { equity: [] }, { crypto: [] }, { etf: [] }, { cash: [] } ]}
I have also tried to use portfolio.$.equity, but that did not work either.
db.users.updateOne(
{_id : ObjectId("6162883719592ea3350d3c87")} ,
{$push : {"portfolio.$.equity" : {name : "AAPL", price : 100, quantity : 1}}}
)
MongoServerError: The positional operator did not find the match needed from the query.
In short, I am trying to append an object to an array inside an object's object.
How can I resolve this error or what is the appropriate way to do this?
You can use arrayFilters with check portfolio.equity field is existed via $exists.
db.users.updateOne({
_id: ObjectId("6162883719592ea3350d3c87")
},
{
$push: {
"portfolio.$[portfolio].equity": {
name: "AAPL",
price: 100,
quantity: 1
}
}
},
{
arrayFilters: [
{
"portfolio.equity": {
$exists: true
}
}
]
})
Sample Mongo Playground

in mongoDB i want to update an object depending upon object's other elements value

I have a collection in MongoDB.
{
"_id" : ObjectId("5aaf51369d8bdfe288d1cb71"),
"companyName" : "ABC",
"name" : "BCD",
"buildInfo" : [
{
"Branch" : "IT",
"Subjects" : [
"Math",
"English",
"Computer",
]
}
],
"currentDate" : ISODate("2018-03-14T14:09:24.374Z"),
"lastModifiedBy" : "ABC.com"
}
I want to insert a new object into "buildInfo" if that branch won't be there. If the branch exists I want to update "Subjects".
I am passing Branch and Subjects to this method.
myDb.collection('ABCDEF').findAndModify(
{'name':'BCD', 'companyName': 'ABC'},
[['_id','asc']],
{
$addToSet: {
'buildInfo.$[i].Subjects': Subjects
}
},
{ upsert: true, arrayFilters: [{ 'i.Branch': Branch }] }
But it's updating if the branch is there, but it's not creating a new object if a branch is not there.
Upsert works on document level only, not for embedded documents.
The simplest way is to issue 3 consecutive updates:
myDb.collection('ABCDEF').findAndModify(
{'name':'BCD', 'companyName': 'ABC', '$or': [{'buildInfo':{'$exists': false}}, {'buildInfo': {'$not':{ '$type': 'array' }}}]},
[['_id','asc']],
{
$set: { 'buildInfo': [] }
},
{ upsert: true}
)
followed by
myDb.collection('ABCDEF').findAndModify(
{'name':'BCD', 'companyName': 'ABC', 'buildInfo.Branch': {$nin: [Branch]}},
[['_id','asc']],
{
$push: {
{ buildInfo: { Branch: Branch, Subjects: [] } }
}
}
)
followed by
myDb.collection('ABCDEF').findAndModify(
{'name':'BCD', 'companyName': 'ABC', 'buildInfo.Branch': Branch},
[['_id','asc']],
{
$addToSet: {
'buildInfo.$[].Subjects': Subject
}
}
)
The first one adds empty buildInfo array to documents that does not have one. The second update adds a subdocument with specific Branch and empty Subjects to documents that don't have the branch. The last query adds Subject to the set.

Mongoose unique index on subdocument

Let's say I have a simple schema:
var testSchema = new mongoose.Schema({
map: { type: [ mongoose.Schema.Types.Mixed ], default: [] },
...possibly something else
});
Now let's ensure that pairs (_id, map._id) are unique.
testSchema.index({ _id: 1, 'map._id': 1 }, { unique: true });
Quick check using db.test.getIndexes() shows that it was created.
{
"v" : 1,
"unique" : true,
"key" : {
"_id" : 1,
"map._id" : 1
},
"name" : "_id_1_map._id_1",
"ns" : "test.test",
"background" : true,
"safe" : null
}
The problem is, this index is ignored and I can easily create multiple subdocuments with the same map._id. I can easily execute following query multiple times:
db.maps.update({ _id: ObjectId("some valid id") }, { $push: { map: { '_id': 'asd' } } });
and end up with following:
{
"_id": ObjectId("some valid id"),
"map": [
{
"_id": "asd"
},
{
"_id": "asd"
},
{
"_id": "asd"
}
]
}
What's going on here? Why can I push conflicting subdocuments?
Long story short: Mongo doesn't support unique indexes for subdocuments, although it allows creating them...
This comes up in google so I thought I'd add an alternative to using an index to achieve unique key constraint like functionality in subdocuments, hope that's OK.
I'm not terribly familiar with Mongoose so it's just a mongo console update:
var foo = { _id: 'some value' }; //Your new subdoc here
db.yourCollection.update(
{ '_id': 'your query here', 'myArray._id': { '$ne': foo._id } },
{ '$push': { myArray: { foo } })
With documents looking like:
{
_id: '...',
myArray: [{_id:'your schema here'}, {...}, ...]
}
The key being that you ensure update will not return a document to update (i.e. the find part) if your subdocument key already exists.
First objectId length in mongodb must be 24. Then you can turn off _id, and rename _id as id or others,and try $addToSet. Good luck.
CoffeeScript example:
FromSchema = new Schema(
source: { type: String, trim: true }
version: String
{ _id: false }//to trun off _id
)
VisitorSchema = new Schema(
id: { type: String, unique: true, trim: true }
uids: [ { type: Number, unique: true} ]
from: [ FromSchema ]
)
//to update
Visitor.findOneAndUpdate(
{ id: idfa }
{ $addToSet: { uids: uid, from: { source: source, version: version } } }
{ upsert: true }
(err, visitor) ->
//do stuff

way to update multiple documents with different values

I have the following documents:
[{
"_id":1,
"name":"john",
"position":1
},
{"_id":2,
"name":"bob",
"position":2
},
{"_id":3,
"name":"tom",
"position":3
}]
In the UI a user can change position of items(eg moving Bob to first position, john gets position 2, tom - position 3).
Is there any way to update all positions in all documents at once?
You can not update two documents at once with a MongoDB query. You will always have to do that in two queries. You can of course set a value of a field to the same value, or increment with the same number, but you can not do two distinct updates in MongoDB with the same query.
You can use db.collection.bulkWrite() to perform multiple operations in bulk. It has been available since 3.2.
It is possible to perform operations out of order to increase performance.
From mongodb 4.2 you can do using pipeline in update using $set operator
there are many ways possible now due to many operators in aggregation pipeline though I am providing one of them
exports.updateDisplayOrder = async keyValPairArr => {
try {
let data = await ContestModel.collection.update(
{ _id: { $in: keyValPairArr.map(o => o.id) } },
[{
$set: {
displayOrder: {
$let: {
vars: { obj: { $arrayElemAt: [{ $filter: { input: keyValPairArr, as: "kvpa", cond: { $eq: ["$$kvpa.id", "$_id"] } } }, 0] } },
in:"$$obj.displayOrder"
}
}
}
}],
{ runValidators: true, multi: true }
)
return data;
} catch (error) {
throw error;
}
}
example key val pair is: [{"id":"5e7643d436963c21f14582ee","displayOrder":9}, {"id":"5e7643e736963c21f14582ef","displayOrder":4}]
Since MongoDB 4.2 update can accept aggregation pipeline as second argument, allowing modification of multiple documents based on their data.
See https://docs.mongodb.com/manual/reference/method/db.collection.update/#modify-a-field-using-the-values-of-the-other-fields-in-the-document
Excerpt from documentation:
Modify a Field Using the Values of the Other Fields in the Document
Create a members collection with the following documents:
db.members.insertMany([
{ "_id" : 1, "member" : "abc123", "status" : "A", "points" : 2, "misc1" : "note to self: confirm status", "misc2" : "Need to activate", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") },
{ "_id" : 2, "member" : "xyz123", "status" : "A", "points" : 60, "misc1" : "reminder: ping me at 100pts", "misc2" : "Some random comment", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") }
])
Assume that instead of separate misc1 and misc2 fields, you want to gather these into a new comments field. The following update operation uses an aggregation pipeline to:
add the new comments field and set the lastUpdate field.
remove the misc1 and misc2 fields for all documents in the collection.
db.members.update(
{ },
[
{ $set: { status: "Modified", comments: [ "$misc1", "$misc2" ], lastUpdate: "$$NOW" } },
{ $unset: [ "misc1", "misc2" ] }
],
{ multi: true }
)
Suppose after updating your position your array will looks like
const objectToUpdate = [{
"_id":1,
"name":"john",
"position":2
},
{
"_id":2,
"name":"bob",
"position":1
},
{
"_id":3,
"name":"tom",
"position":3
}].map( eachObj => {
return {
updateOne: {
filter: { _id: eachObj._id },
update: { name: eachObj.name, position: eachObj.position }
}
}
})
YourModelName.bulkWrite(objectToUpdate,
{ ordered: false }
).then((result) => {
console.log(result);
}).catch(err=>{
console.log(err.result.result.writeErrors[0].err.op.q);
})
It will update all position with different value.
Note : I have used here ordered : false for better performance.