Return child of object array in mongoose - mongodb

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")
}
}

Related

how can I modify a field name / key in a nested array of objects in mongodb?

I have a mongodb collection with a number of objects like this:
{
"_id" : "1234",
"type" : "automatic",
"subtypes" : [
{
"_id" : "dfgd",
"name" : "test subtype",
"subjetRequired" : true,
},
{
"_id" : "dfgd",
"name" : "test subtype2",
"subjetRequired" : false,
}
],
"anotherField" : "some value"
}
As you can see, one of the keys in the subtypes array is incorrectly spelled - "subjetRequired" instead of "subjectRequired".
I want to correct that key name. How can I do that.
I'll preface this by saying I've not worked with mongodb very much in the past.
After a lot of researching, the best I could come up with is the following (which doesn't work):
function remap(doc) {
subtypes = doc.subtypes;
var count = 0;
subtypes.forEach(function(subtype){
db.taskType.update({"_id": subtype._id}, {
$set: {"subtypes.subjectRequired" : subtype.subjetRequired},
$unset: {"subtypes.subjetRequired": 1}
});
}
)
}
db.taskType.find({"subtypes.subjetRequired":{$ne:null}}).forEach(remap);
This doesn't work.
I know the loop is correct, as if I replace the other logic with print statements I can access and print the fields who's names I want to modify.
What am I doing wrong here?
You can use this update and avoid using any code, it's also stable so you can execute it multiple times with no fear.
db.collection.updateMany({
"subtypes.subjetRequired": {
$exists: true
}
},
[
{
$set: {
subtypes: {
$map: {
input: "$subtypes",
in: {
$mergeObjects: [
"$$this",
{
subjectRequired: "$$this.subjetRequired",
}
]
}
}
}
}
},
{
$unset: "subtypes.subjetRequired"
}
])
Mongo Playground
I could modify your loop to override the whole array of subtypes:
function remap(doc) {
correctSubtypes = doc.subtypes.map(({ subjetRequired, ...rest }) => ({
...rest,
subjectRequired: subjetRequired,
}));
var count = 0;
db.taskType.findByIdAndUpdate(doc._id, {
$set: {
subtypes: correctSubtypes,
},
});
}

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

How to populate a field of a subdocument which is an array element of another document?

let collection = await Collection.findOne({ 'works._id': req.params.id }).populate('works.0.photo');
This code will populate the work subdoc in index 0, however I want it to populate the index that corresponds to req.params.id.
I want something like .populate('works.i.photo'), where i represents the index of the work which contains an _id that matches req.params.id.
I figured out how to do it, but I'm certain there's a better way.
let collection = await Collection.findOne({ 'works._id': req.params.id });
const idx = collection.works.findIndex(work => work._id == req.params.id);
collection = await collection.populate(`works.${idx}.photo`).execPopulate();
This doesn't look like the intended way of doing this. Is it possible to do it without iterating to find the index? Preferably with just a single query execution.
Assuming your data is something like this :
someCollection :
/* 1 */
{
"_id" : ObjectId("5dc9c61959f03a3d68cfb8d3"),
"works" : []
}
/* 2 */
{
"_id" : ObjectId("5dc9c72e59f03a3d68cfd009"),
"works" : [
{
"_id" : 123,
"photoId" : ObjectId("5dc9c6ae59f03a3d68cfc584")
},
{
"_id" : 456,
"photoId" : ObjectId("5dc9c6b659f03a3d68cfc636")
}
]
}
photo Collection :
/* 1 */
{
"_id" : ObjectId("5dc9c6ae59f03a3d68cfc584"),
"photo" : "yes"
}
/* 2 */
{
"_id" : ObjectId("5dc9c6b659f03a3d68cfc636"),
"photo" : "no"
}
/* 3 */
{
"_id" : ObjectId("5dc9c6c259f03a3d68cfc714"),
"photo" : "yesno"
}
Mongoose Schemas :
const photoSchema = new Schema({
_id: Schema.Types.ObjectId,
photo: String,
});
const someColSchema = new Schema({
_id: { type: Schema.Types.ObjectId },
works: [{ _id: { type: Number }, photoId: { type: Schema.Types.ObjectId, ref: 'photo' } }]
});
const someCol = mongoose.model('someCollection', someColSchema, 'someCollection');
const photoCol = mongoose.model('photo', photoSchema, 'photo');
Code :
1) Using Mongoose populate (Mongoose Populate) :
let values = someCol.find({"works._id": 123}, {_id: 0, 'works.$': 1}).populate('works.photoId').lean(true).exec();
2) Using mongoDB's native $lookup (mongoDB $lookup) :
someCol.aggregate([{ $match: { 'works._id': 123 } }, { $unwind: '$works' }, { $match: { 'works._id': 123 } }, {
$lookup:
{
from: "photo",
localField: "works.photoId",
foreignField: "_id",
as: "doc"
}
}, { $project: { _id: 0, doc: { $arrayElemAt: ["$doc", 0] } } }])
Both should work similar, in aggregation we're doing $match to filter given criteria & $unwind to unwrap works array & again doing a filter to only retain values in array that match filter criteria & then doing $lookup to fetch respective document from other collection.

Trim string values of whitespace, from an array of sub-documents with string field

On all documents of my collection I want to perform a $trim operation to a specific field of an object that is in an array.
Example:
{
"_id" : ObjectId("53857680f7b2eb611e843a32"),
"company": Testcompany
"customer" :
"name": Testuser,
"addresses" : [
{
"_id" : ObjectId("5d6d2f96e3fdc8001077ac6c"),
"street" : "Teststreet. ",
"houseNr" : "133",
},
{
"_id" : ObjectId("5d6d2f96e3fdc8001077ac7b"),
"street" : " Simplestreet. ",
"houseNr" : "12",
}
],
}
In the example, I want to $trim all values of the field: "customer.addresses.street"
To answer the upcoming questions:
I know the article you mentioned (Removing white spaces (leading and trailing) from string value) but theres no example how to do it within an array.
My problem is, how to access the attributes within an array, heres the example of plain values:
[{ $set: { category: { $trim: { input: "$category" } } } }],
Yes, I want to update the values of all documents within the collection
One possible way to do:
db.YOUR_COLLECTION.find({}).forEach(
function(doc) {
db.Trim.update(
{ "_id":doc._id },
{
"$set": {
"customer.addresses":doc.customer.addresses.map(
function(child) {
return Object.assign(
child,
{ street: child.street.trim() }
)
}
)
}
}
)
}
)
Obs: Solution with Javascript Executed in MongoShell.
You can use $map and $trim in an updateMany aggregation pipeline like this :
db.YOUR_COLLECTION.updateMany({"customer.addresses":{$ne:null}},[
{
$set: {
"customer.addresses":
{
$map: {
input: "$customer.addresses",
as: "address",
in: { $trim: { input: "$$address" } }
}
}
}
}
])

MongoDB $set item in array which does not exists with object as element results in object instead of array

Updating elements inside an array is explained here:
https://docs.mongodb.com/manual/reference/operator/update/set/#set-elements-in-arrays
But in the docs seems the case missing where the array does not exists yet in the document. Example:
If have this document:
{
_id: 'some-id',
otherprops: 'value'
}
And I want it to become like this:
{
// ...
settings: {
friends: [
{
name: 'John Doe',
age: 28
}
]
}
// ...
}
In this situation I dont know if there is already a settings.friends array. So my query looks like this:
{
$set: {
'settings.friends.0.name': 'John Doe',
'settings.friends.0.age': 28
}
}
However the document results in like this:
{
// ...
settings: {
friends: {
0: {
name: 'John Doe',
age: 28
}
}
}
// ...
}
Is there a way to force mongodb to create an array instead of an object in my example but with using dot notations only.
try this.
{
$addToSet: {
"settings.friends": [
{
name: 'John Doe',
age: 28
}
]
}
}
This is working in mongodb version v4.0.9. I have tested your usecase in my machine.
db.collection.update(
{
"_id": 'your_key'
},
{
"$set": {
"settings.friends.0.name": "ABCD"
}
},
{
"upsert": "true"
}
)
Before Update my collection entry was
{
"_id" : 2,
"col1" : "val01",
"settings" : {
"friends" : [
{
"name" : "Ramraj",
"age" : 28
}
]
}
}
After update my collection entry was
{
"_id" : 2,
"col1" : "val01",
"settings" : {
"friends" : [
{
"name" : "ABCD",
"age" : 28
}
]
}
}