Fetch sub-document Mongodb only match with criteria - node.js

I have data in mongodb like this:
{
"_id" : ObjectId("55a12bf6ea1956ef37fe4247"),
"tempat_lahir" : "Paris",
"tanggal_lahir" : ISODate("1985-07-10T17:00:00.000Z"),
"gender" : true,
"family" : [
{
"nama" : "Robert Deniro",
"tempat_lahir" : "Bandung",
"tanggal_lahir" : ISODate("2015-07-09T17:00:00.000Z"),
"pekerjaan" : "IRT",
"hubungan" : "XXX",
"tanggungan" : false,
"_id" : ObjectId("55a180f398c9925299cb6e90"),
"meta" : {
"created_at" : ISODate("2015-07-11T20:59:25.242Z"),
"created_ip" : "127.0.0.1",
"modified_at" : ISODate("2015-07-12T15:54:39.682Z"),
"modified_ip" : "127.0.0.1"
}
},
{
"nama" : "Josh Groban",
"tempat_lahir" : "Jakarta",
"tanggal_lahir" : ISODate("2015-06-30T17:00:00.000Z"),
"pekerjaan" : "Balita",
"hubungan" : "Lain-Lain",
"tanggungan" : true,
"_id" : ObjectId("55a29293c65b144716ca65b2"),
"meta" : {
"created_at" : ISODate("2015-07-12T16:15:15.675Z"),
"created_ip" : "127.0.0.1"
}
}
]
}
when i try to find data in sub-document, with this code:
person.findOne({ _id: req.params.person, {'family.nama': new RegExp('robert', 'gi') }}, function(err, data){
// render code here
});
It show all data in Family Data,
Can we fetch or display a data only match with criteria/keyword, for example only "Robert Deniro" row
Thank You

In 'regular' MongoDB, you can use the $ operator for that. I'm not sure if it works with Mongoose, but it's worth a try:
person.findOne({
_id : req.params.person,
'family.nama' : new RegExp('robert', 'gi')
}, {
// Only include the subdocument(s) that matched the query.
'family.$' : 1
}, function(err, data){
// render code here
});
If you need any of the properties from the parent document (tempat_lahir, tanggal_lahir or gender; _id will always be included), you need to add them to the projection object explicitly.
One caveat: the $ operator will only return the first matching document from the array. If you need it to return multiple documents, you can't use this method and (AFAIK) have to postprocess the results after they are returned from the database.

It solved with this code:
var options = {
family: {
$elemMatch: { nama: req.query.keyword }
},
};
person.findOne({ _id: req.params.person, 'family.nama': keyword }, options, function(err, data){
//render code here
});
Thanks to #hassansin & #robertklep

Related

Prevent mongoose "Model.updateOne" from updating ObjectId(_id) of the model when using "$set"

I'm updating the age and name of a character with a specific _id from an array of characters that is inside a document of model Drama.
The document I'm working with:-
{
"_id" : ObjectId("619d44d2ec2ca20ca0404b5a"),
"characters" : [
{
"_id" : ObjectId("619fdac5a03c8b10d0b8b13c"),
"age" : "23",
"name" : "Vinay",
},
{
"_id" : ObjectId("619fe1d53810a130207a409d"),
"age" : "25",
"name" : "Raghu",
},
{
"_id" : ObjectId("619fe1d53810a130207a502v"),
"age" : "27",
"name" : "Teju",
}
],
}
So to update the character Raghu I did this:-
const characterObj = {
age: "26",
name: "Dr. Raghu",
};
Drama.updateOne(
{ _id: req.drama._id, "characters._id": characterId },
{
$set: {
"characters.$": characterObj,
},
},
function(err, foundlist) {
if (err) {
console.log(err);
} else {
console.log("Update completed");
}
}
);
// req.drama._id is ObjectId("619d44d2ec2ca20ca0404b5a")
// characterId is ObjectId("619fe1d53810a130207a409d")
This updated the character but it also assigned a new ObjectId to the _id field of the character. So, I'm looking for ways on how to prevent the _id update.
Also, I know I can set the individual fields of character instead of assigning a whole new object to prevent that but it will be very tedious if my character's object has a lot of fields.
//Not looking to do it this way
$set: {
"characters.$.age": characterObj.age,
"characters.$.name": characterObj.name,
},
Thanks.
I found something here, just pre define a schema (a blueprint in a way) that affects the id
var subSchema = mongoose.Schema({
//your subschema content
},{ _id : false });
Stop Mongoose from creating _id property for sub-document array items
Or I would say, when you create a character assign it a custom id from the start, that way it will retain that id throughout.
I'm leaving this question open as I would still like to see a simpler approach. But for now, I did find one easy alternative solution for this issue which I'm will be using for some time now until I find a more direct approach.
In short - Deep merge the new object in the old object using lodash and then use the new merged object to set field value.
For example, let's update the character Raghu from my question document:-
First install lodash(Required for deep merging objects) using npm:
$ npm i -g npm
$ npm i --save lodash
Import lodash:
const _ = require("lodash");
Now update the character Raghu like this:-
const newCharacterObj = {
age: "26",
name: "Dr. Raghu",
};
Drama.findById(
{ _id: req.drama._id, "characters._id": characterId },
"characters.$",
function(err, dramaDocWithSpecificCharacter) {
console.log(dramaDocWithSpecificCharacter);
// ↓↓↓ console would log ↓↓↓
// {
// "_id" : ObjectId("619d44d2ec2ca20ca0404b5a"),
// "characters" : [
// {
// "_id" : ObjectId("619fe1d53810a130207a409d"),
// "age" : "25",
// "name" : "Raghu",
// }
// ],
// }
const oldCharacterObj = dramaDocWithSpecificCharacter.characters[0];
const mergedCharacterObjs = _.merge(oldCharacterObj, newCharacterObj);
// _.merge() returns a deep merged object
console.log(mergedCharacterObjs);
// ↓↓↓ console would log ↓↓↓
// {
// _id: 619fe1d53810a130207a409d,
// age: "26",
// name: "Dr. Raghu",
// };
Drama.updateOne(
{ _id: req.drama._id, "characters._id": characterId },
{
$set: {
"characters.$": mergedCharacterObjs,
},
},
function(err, foundlist) {
if (err) {
console.log(err);
} else {
console.log("Update completed");
}
}
);
}
);
// req.drama._id is ObjectId("619d44d2ec2ca20ca0404b5a")
// characterId is ObjectId("619fe1d53810a130207a409d")
Note: We can also use the native Object.assign() or … (spread operator) to merge objects but the downside of it is that it doesn’t merge nested objects which could cause issues if you later decide to add nested objects without making changes for deep merge.
You can pass your payload or request body like this if we provide _id it will prevent update to nested document
"characters" : [
{
"_id" : "619fdac5a03c8b10d0b8b13c",
"age" : "updated value",
"name" : "updated value",
}, {
"_id" : "619fe1d53810a130207a409d",
"age" : "updated value",
"name" : "updated value",
}, {
"_id" : "619fe1d53810a130207a502v",
"age" : "updated value",
"name" : "updated value",
}
],
It works for me for bulk update in array object

How do i retrieve just the child-object in Azure Cosmos using mongoose and Node.js?

I am using Azure cosmos db with the Mongodb API. Also i am using mongoose to create schemas and create new documents in the database. I am also using Node.js.
At this point I am considering using a One-to-Many relationship with embedded documents.
The data structure is like this :
{
"_id" : "locality1",
"_type" : "Locality",
"name" : "Wallmart",
"subsectionList" : [
{
"_id" : "subsection1",
"_type" : "SubSection",
"name" : "First floor",
"sensorList" : [
{
"_id" : "sensor1",
"_type" : "Sensor",
"placement" : "In the hallway"
},
{
"_id" : "sensor2",
"_type" : "Sensor",
"placement" : "In the ceiling"
}
]
},
{
"_id" : "subsection2",
"_type" : "SubSection",
"name" : "Second floor",
"sensorList" : [ ],
}
],
}
I want to retrieve ONLY the "sensor1"-object, not anything from the parent.
Using querying i am only able to retrieve the entire "locality1"-object, with all its underlying subsections and sensors. On a larger scale that is an unnecessary large amount of data.
Here is my query so far.
Locality.find().where('subsectionList.sensorList._id').equals("sensor1").then(doc => {
console.log(doc)
})
I appreciate any tips! :)
Based on my test, i can't get rid of the _id property anyway even though i followed the parameters which is mentioned here.
Locality.find({},'subsectionList', function (err, locas)
The above query still return the results including _id property.(It seems a default item)
I get a workaround from this blog that you could loop the array to filter your desired columns.
var mongoose = require('mongoose');
var COSMOSDB_CONNSTR= "mongodb://***.documents.azure.com:10255/db";
var COSMODDB_USER= "***";
var COSMOSDB_PASSWORD= "***";
mongoose.connect(COSMOSDB_CONNSTR+"?ssl=true&replicaSet=globaldb", {
auth: {
user: COSMODDB_USER,
password: COSMOSDB_PASSWORD
}
}).then(() => console.log('Connection to CosmosDB successful'))
.catch((err) => console.error(err));
const Locality = mongoose.model('Locality', new mongoose.Schema({
_id: String,
subsectionList: [{
sensorList: [{
_id: String,
_type: String,
placement: String
}]
}]
}));
Locality.find({},'subsectionList', function (err, locas) {
if (err) return handleError(err);
var returnArray = [];
for(var i = 0; i<locas.length;i++){
for(var j = 0; j<locas[i].subsectionList.length;j++){
for(var k = 0; k<locas[i].subsectionList[j].sensorList.length;k++){
if(locas[i].subsectionList[j].sensorList[k]._id == 'sensor1')
returnArray.push(locas[i].subsectionList[j].sensorList[k]);
}
}
}
console.log(returnArray);
});

Using MongoDB/NodeJS, how can I increment by the number of documents modified in an update query?

I have written an update query in MongoDB/NodeJS that deletes objects from an array of a document, based on the parameters I define. After I pull these objects, I would like to to increment another variable in the document based on how many documents were modified by the update query.
Here is an example of one of my events documents:
{
"_id" : ObjectId("575ed7fca7b89bb4027dded9"),
"dateCreated" : "6/13/2016",
"latitude" : "56.294786195890076",
"longitude" : "-43.59161567687988",
"instructorName" : "Test User",
"instructorEmail" : "test#user.com",
"instructorRating" : 5,
"eventName" : "We gon exercise",
"eventDescription" : "We gon exercise",
"spacesAvailable" : 15,
"streetAddress" : "123 wer",
"city" : "rty",
"state" : "NY",
"zip" : "12332",
"date" : "06/21/2016",
"startTime" : "12:00",
"endTime" : "02:10",
"tags" : [
"Cardio",
"Crossfit"
],
"price" : 5,
"attendies" : [
{
"_id" : ObjectId("5759cfcdb71d80fb2d1203ef"),
"name" : "Buddy Loester",
"email" : "Bud18#gmail.com",
"timeStamp" : 1467048318510,
"payed" : true
},
{
"_id" : ObjectId("574f257b05086e2c7f7940ca"),
"name" : "Trainer Trainer",
"email" : "trainer#user.com",
"timeStamp" : 1467055627894,
"payed" : true
}
],
"unpayed" : 0
}
Here is my code to give a better visualization:
var eventCollection = req.db.get('events');
// get current time since epoch in milliseconds
var milliSinceEpoch = new Date().getTime();
eventCollection.update(
{"attendies.payed" : {$eq : false}},
{
$pull:
{
"attendies" : {"timeStamp": {$lt: milliSinceEpoch /*- 600000*/}}
},
$inc:
{
spacesAvailable: numberAffected
}
},
{
multi: true
}, function(err, numberAffected) {
console.log(numberAffected);
return res.end();
}
);
If I specify 'numberAffected' in the query portion to '1', then it works as expected and increments by 1. However, I would like to increment by the number affected.
I know this code will not work with 'numberAffected' in the query. Using 'numberAffected' in the callback actually does return the number of documents modified by my query.
Does there exist a way in MongoDB to do what I am trying to do?
I have solved my problem by rewriting the query. It is as follows:
var ObjectID = require("mongodb").ObjectID;
var eventCollection = req.db.get('events');
var milliSinceEpoch = new Date().getTime();
// find and return all the documents in the events DB where there is a user who has not payed for an event
// they RSVP'd for
eventCollection.find({"attendies.payed" : {$eq : false}}, function(err, documentsWithUnpayedUsers) {
// if error finding, print it and return
if(err) {
console.log(err);
return res.sendStatus(400, "Error cancelling");
}
// if everyone has payed for all RSVP'd events
if(!documentsWithUnpayedUsers) return res.sendStatus(404, "Everyone has payed!");
// loop through every document which has people who have not yet payed for RSVP'd events
for(var i = 0; i < documentsWithUnpayedUsers.length; i++) {
// for each of these documents:
eventCollection.update(
{_id: ObjectID(documentsWithUnpayedUsers[i]._id)},
{
// remove the user from the attendie list if they have not payed,
// and it has been 10 minutes since they RSVP'd
$pull:
{
"attendies" : {"timeStamp": {$lt: milliSinceEpoch - 600000}, "payed" : {$eq : false}}
},
// then modify the number of spaces available for the event by the number of people who were
// removed from the attendie list
// then modify the amount of people who have not payed for the event yet (will now be 0)
$inc:
{
spacesAvailable: documentsWithUnpayedUsers[i].unpayed,
unpayed: -documentsWithUnpayedUsers[i].unpayed
}
}, function(err) {
// error checking for the update query
if(err){
console.log(err);
return res.sendStatus(400, "There was an error removing an attendie fom the event: "
+ documentsWithUnpayedUsers[i].eventName);
}
}
); // end of update query
} // end of for loop
return res.end();
}
); // end of find()
}); // end of checkPayed

$addToSet in a nested Array item

Considering the following
User collection & sample User document
{
"_id" : ObjectId("575c01f7b8e5999addeb598c"),
"username" : "test.1#gmail.com",
"password" : "<password>",
"firstName" : "Test,
"lastName" : "User"
}
I am trying to run an update request to add an entry in userData.eventData which is meant to be an array
In mongo script I can do
> db.Users.update({_id: ObjectId("575c01f7b8e5999addeb598c")}, {"$addToSet":{"userData.eventData":"My event"}} )
And I have the following result : userData is created as an Object and eventData as a nested Array
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.Users.find({_id: ObjectId("575c01f7b8e5999addeb598c")})
{ "_id" : ObjectId("575c01f7b8e5999addeb598c"), "username" : "test.1#gmail.com", "password" : "<password>", "firstName" : "Test", "lastName" : "User", "userData" : { "eventData" : [ "My event" ] } }
While running the same logic in mongo (using driver version 2.1.21)
// with a properly initialized db object
db.collection("Users").update({"_id" : ObjectId("575c01f7b8e5999addeb598c")}, {"$addToSet": { "userData.eventData": "My Event"}}, function(err, result) {
// do something
});
I receive the following response
result:Object
n:0
nModified:0
ok:1
And indeed the database entry is unchanged.
Is that the way it is meant to behave? I can easily fix this by creating the userData.eventData array but I found disturbing the fact that node's Mongo driver and mongo shell didn't behave the same on this
Thanks in advance for your help & advice
Edit 13/6/16
Mistake was on my side, I missed a 'new' before 'ObjectId(...' in node. With it, it behaves exactly the same as in mongo shell (i.e. 'userData' is created as an Object and it includes 'eventData' array)
No issue, then :)
Update, updates an already existing object in your document.
What you want is insert or use upset which creates a new document when no document matches the query criteria
db.collection.update(
{ name: "Andy" },
{
name: "Andy",
rating: 1,
score: 1
},
{ upsert: true }
);
If you wanted to add an object to your array, you would need $push
// Insert a document in the capped collection
function push (db, collection, search, data, callback) {
db.collection(collection).update(
search, {
$push: data
}, function (err, result) {
console.log("New object pushed");
callback(err);
});
}

mongodb deep update - mongoose .id() causing performance issue

mongoose level collection schema
{
sublevel: [{
"deeplevel": [{
}],
"deeplevel2": [{
}],
}]
}
//routes.js
dlDoc = { "dl1": "dl1" };
db.levels.update({ _id: ObjectId(levelId), "sublevel._id": ObjectId(sublevelId) }, { $push: { "sublevel.$.deeplevel1": dlDoc } }, function (err, updatedDoc) {
if (updatedDoc) {
res.json({ "status": 1 });
res.end();
//calling external apis to update more
result = externalApiResult();
db.levels.findById(level._id, 'sublevel._id sublevel.deeplevel1', function (err, levelFound) {
levelFound.sublevel.id(sublevelId).deeplevel.id(dlDoc._id)['result'] = result;
levelFound.save(function (err, savedDoc) {
});
})
}
});
I need to create and update the level collection's deeplevel1. in my case, the deeplevel1 will be inserted more than 2000 sub docs per day. when i want to update using mongoose .id(deepLevelId) function it is causing me performance issues in the server. is there any way to find the position of deeplevel document inserted once i update the level collection?. so that i can use the position in the update query. And please tell me a best way to update my deeplevel subdocuments without causing performance issues and works fast. thanks in advance.
db.collection.findOne() will be like this.
{
"_id" : ObjectId("563b35d1f07cc9d80a26436b"),
"name":"name",
sublevel:[{
"_id" : ObjectId("569bede3c717b670097519c7"),
"name" : "name",
"deeplevel2" : [{
"_id" : ObjectId("569c559329cc880c18989349"),
"logTime" : new Date("1/18/2016 03:30:48"),
"areaId" : ObjectId("568a6129de552e7dba98d547"),
}, {
"_id" : ObjectId("569c561929cc880c18989354"),
"logTime" : new Date("1/18/2016 03:30:48"),
"areaId" : ObjectId("568a6129de552e7dba98d547"),
}, {
"_id" : ObjectId("569c5945626ffb680e4512e9"),
"logTime" : new Date("1/18/2016 03:30:48"),
"areaId" : ObjectId("568a6129de552e7dba98d547"),
}, {
"_id" : ObjectId("569c594d626ffb680e4512eb"),
"logTime" : new Date("1/18/2016 03:30:48"),
"areaId" : ObjectId("568a6129de552e7dba98d547"),
}
]
}]
}

Resources