Append an object to an array inside a nested object - mongodb

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

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

$mul nested document field where might not exist

I have the following document:
{
"results" : [
{
"name" : "foo",
"score" : 10
},
{
"name" : "bar"
}
]
}
I want to multiply the score field by 10, only where it exists.
Using just dot notation:
{
$mul: {
'results.score': NumberInt(10)
}
}
Returns an error:
Cannot create field 'score' in element {results: [ { name: "foo", score: 10 }, { name: "bar" } ]}
I've tried using the new array filters:
{
$mul: {
'results.$[result].score': NumberInt(10)
}
}, {
arrayFilters: [{
'result.grade': {
$exists: true
}
}]
}
This gives me an error too:
No array filter found for identifier 'result' in path 'results.$[result].score'
I know that I could set a score field in all the documents and set it to zero, that wouldn't be a solution though as the lack of a score means that there isn't a score yet, rather that there is a score and it's 0.
Can this be done?
Could this be done prior to version 3.6?

How to add key to $addToSet in mongoDB

I want to add a key inside mongodb add function. I am doing this right now.
$addToSet : {
"msges":{
time:{"from":uname,"title":title,"msg":msg,"read":false}
}
}
time is a variable that is coming from the paramater. It has time inside it as hh:mm:ss A. But when the query runs, instead of time as key, string "time" gets print as key. Any ideas what should I do?
Enclose your variable in [] :
$addToSet: {
"msges": {
[time]: { "from": uname, "title": title, "msg": msg, "read": false }
}
}
For instance :
var myfield = "custom_field";
db.test.update({
_id: 1
}, {
$addToSet: {
letters: [{
[myfield]: 1
}, {
[myfield]: 2
}]
}
})
It gives :
{ "_id" : 1, "letters" : [ [ { "custom_field" : 1 }, { "custom_field" : 2 } ] ] }

Return child of object array in mongoose

I would like to return the matching object from an array of objects in mongoose/mongodb but I can't seem to get it right.
My schema currently looks like this:
items: {
left: { type: Number, default: 0 },
total: { type: Number, default: 0 },
each: [{
name: String
}]
}
This makes each object within each to get its own object id. Now I am trying to query this with mongoose, I've tried both $in and $elemMatch and a plain .find({ items.each._id: req.params.id }).
More specific
Project.findOne({ 'items.each': { $elemMatch: { _id: req.params.id } } }).exec()
I want to return an object like this:
{
_id: ObjectId(23426456234),
name: "My name is"
}
But why can't I get this?
Use:
db.getCollection('projects').findOne({"items.each._id" :
ObjectId("57ffc4396270adff8b273f72")},{"items.each.$":1})
Output:
{
"_id" : ObjectId("57ffc4396270adff8b273f71"),
"items" : {
"each" : [
{
"name" : "sdfsd",
"_id" : ObjectId("57ffc4396270adff8b273f72")
}
]
}
}
OR:
db.getCollection('projects').aggregate(
{$unwind: "$items.each"},
{$match:{"items.each._id" : ObjectId("57ffc4396270adff8b273f72")}},
{$project:{_id: 0, each: "$items.each"}}
)
Output:
{
"each" : {
"name" : "sdfsd",
"_id" : ObjectId("57ffc4396270adff8b273f72")
}
}

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.