Querying nested object using $find by applying conditions in Mongodb - node.js

This is my json object
{
"account_id" : "1",
"sections" : [
"name" : "sec1",
"label" : {
"label1" : "text1",
"label2" : "text2"
}
},
"name" : "sec2",
"label" : {
"label3" : "text3",
"label4" : "text4",
"label5" : "text5"
}
},
]
}
So in this json I wanted to query the label object where sector= sec1. I have used the below code but it didn't work.
var getData = (db, query) => {
return db
.collection(TABLE_NAME)
.find(query, { account_id: { sections: { label: 1 } } })
.toArrayAsync();
};
var dataList = (db, event) => {
let dataQuery = {
account_id: id,
'sections.name': event.params.section
};
return getData(db, dataQuery);
};
module.exports.getData = (event, cb) => {
return using(connectDatabase(), db => {
return dataList (db, event);
}).then(data => cb(null, responseObj(data, 200)), err =>
cb(responseObj(err, 500)));
};
Could someone kindly help me? Thanks inadvance.

Try something like this. use $project, we can selectively remove or retain field and we can reassign existing field values and derive entirely new values. after projecting the labels and name do a $match to extract the document by name. One thing to notice is that by using $project,it will automatically assign the document's _id.
var dataList = (db, event) => {
return db
.collection(TABLE_NAME)
.aggregate([
{
$match: { account_id: your_id }
},
{
$unwind: '$sections'
},
{
$project:{labels:'$sections.label',name:'$sections.name'}
},
{
$match:{name:section_name}
}]).toArray();
};

You have to use aggregate method with $unwind syntax to find item in array of object.
var dataList = (db, event) => {
return db
.collection(TABLE_NAME)
.aggregate([
{
$match: {
account_id: id,
}
},
{ $unwind: "$comments" },
{
$match: {
'name': event.params.section
}
}
])
.toArrayAsync();
};
Result:
[{
"account_id": "1",
"sections": {
"name": "sec2",
"label": {
"label3": "text3",
"label4": "text4",
"label5": "text5"
}
}
}]

Related

how to update an object of an element in array in mongodb?

This is the structure i have, i want to update the nested array element if an object key matches for example - i want to match grnno :"10431000" and update the other keys of that object like vehicle_no,invoice_no etc.
{
"_id" : ObjectId("5f128b8aeb27bb63057e3887"),
"requirements" : [
{
"grns" : [
{
"invoice_no" : "123",
"vehicle_no" : "345",
"req_id" : "5f128c6deb27bb63057e388a",
"grnno" : "10431000"
},
{
"invoice_no" : "abc",
"vehicle_no" : "def",
"req_id" : "5f128c6deb27bb63057e388a",
"grnno" : "10431001"
}
]
}
]
}
I have tried this code
db.po_grn.update({
"requirements.grns.grnno":"10431001"
}, {
$set: {
"requirements.$.grns": {"invoice_no":"test",vehicle_no:"5455"}
}
})
But this is changing the structure i have like this
"requirements" : [
{
"grns" : {
"invoice_no" : "test",
"vehicle_no":"5455"
},
"req_id" : ObjectId("5f128b8aeb27bb63057e3886")
}
],
grns key should be array, and update should be of the particular object which matches the key "grnno". Please help me out. Thanks.
==Edit==
var grnno = req.body.grnno;
db.po_grn.find({
"requirements.grns.grnno":grnno
}).toArray(function(err, po_grn) {
console.log("po_grn",po_grn);
if (po_grn.length > 0) {
console.log("data.grn.grnno ", grnno);
var query = {
requirements: {
$elemMatch: {
"grns.grnno": grnno
}
}
};
var update = {
$set: {
'requirements.$[].grns.$[inner].invoice_no': data.invoice_no,
'requirements.$[].grns.$[inner].vehicle_no': data.vehicle_no,
}
};
var options = {
arrayFilters: [
{ "inner.grnno" : grnno }
]
};
db.po_grn.update(query, update, options
, function(er, grn) {
console.log("grn",grn,"er",er)
res.send({
status: 1,
message: "Grn updated successfully"
});
}
);
} else {
res.send({
status: 0,
message: "Grn not found "
});
}
})
Use a combination of $[] positional-all operator with array filters to update your inner nested document.
var query = {
requirements: {
$elemMatch: {
"grns.grnno": "10431001"
}
}
};
var update = {
$set: {
'requirements.$[].grns.$[inner].invoice_no': "test",
'requirements.$[].grns.$[inner].vehicle_no': "5455",
}
};
var options = {
arrayFilters: [
{ "inner.grnno" : "10431001" }
]
};
db.collection.update(query, update, options);
Update -
NodeJS native MongoDb driver code attached, which is working fine
const { MongoClient } = require('mongodb');
const url = "mongodb://localhost:27017/";
MongoClient.connect(url, function(err, db) {
if (err) {
throw err;
}
const dbo = db.db("test");
(async() => {
const query = {
requirements: {
$elemMatch: {
"grns.grnno": "10431001"
}
}
};
const update = {
$set: {
'requirements.$[].grns.$[inner].invoice_no': "test",
'requirements.$[].grns.$[inner].vehicle_no': "5455",
}
};
const options = {
arrayFilters: [
{ "inner.grnno" : "10431001" }
],
multi: true
};
try {
const updateResult = await dbo.collection("collection").update(query, update, options);
} catch (err) {
console.error(err);
}
db.close();
})();
});

Find when the keys are unkown in mongodb

How can I getthe data that has email as abc#gmail.com in mongoDB?I don't know the Key Name and I want to iterate through all the data.
I have data like this:
{
"_id":"5c0a1589a5a41b2ae707317b",
"test1":{
"email":"abc#gmail.com",
"phoneNo":"123456897",
"endpointId":"test1"
}
}
{
"_id":"5c0a1989a5a41b2ae807317b",
"test2":{
"email":"abc#gmail.com",
"phoneNo":"123456897",
"endpointId":"test2"
}
}
{
"_id":"5c0a1989a5a41b2ae807317b",
"test2":{
"email":"pqr#gmail.com",
"phoneNo":"123456897",
"endpointId":"test3"
}
}
But the object key is not known at the time of searching. I want to iterate through all the data and get matched data that has specific email.
If I know the key name like test1,test2 etc then I can use find({test1:{...}}) but Here I don't know the key value.
So, how can I do that?
You can use below aggregation using $objectToArray in mongodb 3.4 and above
db.collection.aggregate([
{ "$addFields": {
"field": { "$objectToArray": "$$ROOT" }
}},
{ "$match": { "field.v.email": "abc#gmail.com" }},
{ "$project": { "field": 0 }}
])
I am assuming you get the objects in array type.
I made a method named findObject. This method will take the object array and the desired email.
Finally, return the first object, that matched with the email.
const data = [{
"_id":"5c0a1589a5a41b2ae707317b",
"test1":{
"email": "abc#gmail.com",
"phoneNo": "123456897",
"endpointId":"test1"
}
},
{
"_id":"5c0a1989a5a41b2ae807317b",
"test2":{
"email": "abc#gmail.com",
"phoneNo": "123456897",
"endpointId":"test2"
}
},
{
"_id":"5c0a1989a5a41b2ae807317b",
"test2":{
"email": "pqr#gmail.com",
"phoneNo": "123456897",
"endpointId": "test3"
}
}];
const findObject = (data, email) => {
for (let index=0; index<data.length; index++) {
const currentData = data[index];
for (let property in currentData) {
if (property != '_id' && currentData[property].email == email) {
return currentData;
}
}
}
return null;
}
let desiredObject;
const desiredEmail = 'abc#gmail.com';
desiredObject = findObject(data, desiredEmail);
console.log(desiredObject);
And the output will be
{ _id: '5c0a1589a5a41b2ae707317b',
test1:
{ email: 'abc#gmail.com',
phoneNo: '123456897',
endpointId: 'test1' } }
I think you can't do query on totally unknown field! if you could change your schema see here for more info, also you could write script to migrate to a new DB with new schema:
// new doc instance
{
"_id":"5c0a1589a5a41b2ae707317b",
"obj": {
"name": "test1"
"email":"abc#gmail.com"
"phoneNo":"123456897",
"endpointId":"test1"
}
},
{
"_id":"5c0a1989a5a41b2ae807317b",
"obj": {
"name": "test2"
"email":"abc#gmail.com"
"phoneNo":"123456897",
"endpointId":"test2"
}
},
{
"_id":"5c0a1989a5a41b2ae807317b",
"obj": {
"name": "test3"
"email":"pqr#gmail.com"
"phoneNo":"123456897",
"endpointId":"test3"
}
}
otherwise, check this may works correctly. if all of them is not effective so make a query to get all of your data as an Array and use filter method on it:
Model.find({}, (err, docs) => {
const result = docs.filter((doc) => {
for (key in doc) {
if (doc[key].email === 'abc#gmail.com')
return doc;
}
});
console.log(result);
});

How to remove object from an Array which is under an object Mongodb

I have a data set like this:
{
"_id" : ObjectId("5bacc98431481e0520856df8"),
"action" : {
"count" : 0,
"shop" : [
{
"uid" : ObjectId("5c0b396a50b8a627c4172a2b"),
},
{
"uid" : ObjectId("5c0b3b471ed11f2124e1e3a8"),
},
{
"uid" : ObjectId("5c0b3b471ed11f2124e1e3a9"),
}
]
}
}
How will I remove the above object whose uid is ObjectId("5c0b3b471ed11f2124e1e3a8") through mongodb Query?
I used with the approach which is not perfect according to me. My approach is like this:
db.CollectionName.find({_id: ObjectId("5bacc98431481e0520856df8")})
.then(data => {
if (data.length > 0) {
let xData = data[0].notifications.shop;
let xuid = ObjectId("5c0b3b471ed11f2124e1e3a8");
let filterData = xData.filter(
x => x.uid!= xuid
);
User.update(
{ _id: ObjectId("5bacc98431481e0520856df8")},
{
$set: {
action: { shop: filterData }
}
}
)
.then(usr => {
console.log("deleted successfully")
})
.catch(er => {
console.log(er)
})
}
})
.catch(error => {
console.log(error)
})
By this approach I remove the uid from an array which itself under an object. If anyone knows this type of task done through MongoDB Query then please let me know.
Any Help/Suggestion is really appreciated. Thanks in advance for the developer who attempted my Query.
The mongo statement to do this would be:
db.getCollection('YOURCOLECTION').update(
{'_id': ObjectId("5bacc98431481e0520856df8")},
{ "$pull":
{ "action.shop": { "uid": ObjectId("5c0b3b471ed11f2124e1e3a8") }}
})
You would be using $pull command in combination with the $update.

Use variable in mongodb object dot notation

I want to increment a property value of an object if it does exist inside an array.
Mongo record:
{
"_id" : ObjectId("5b7bdd9f0465e8345ba83aad"),
"userID" : "400",
"userName" : "Jon Snow",
"pageName" : "1",
"courseName" : "Maths",
"socketID" : [
"aswKWYyE1euk2GNIAAAD"
],
"online" : true,
"userHistory" : {
"pagesVisited" : [
{
"page" : "1",
"timesVisited" : 1
}
],
"coursesVisited" : [
"Maths"
]
},
"date" : ISODate("2018-08-21T09:38:39.281Z")
}
Here on userHistory.pagesVisited on the page property if I get the value 1 again then I want to increment the timesVisited property like so:
"pagesVisited" : [
{
"page" : "1",
"timesVisited" : 2
}
],
Here's what I have tried with no luck:
let userDetails = {
userID: queryUser.userID,
userName: queryUser.username,
pageName: queryUser.pageName,
courseName: queryUser.courseName,
socketID: [socket.id],
online: true,
userHistory: {
pagesVisited: [
{
"page" : queryUser.pageName,
"timesVisited" : 1
}
],
coursesVisited: [queryUser.courseName]
},
date: new Date()
};
onlineUsersDB.findOne({'userID': userDetails.userID}).then(async (user) => {
if (user) {
let page = {"page": queryUser.pageName, "timesVisited": 1};
let course = queryUser.courseName;
let updatedUser = await onlineUsersDB.findOneAndUpdate(
{'userID': user.userID},
{
$set: {'online': true},
$push: { 'socketID': socket.id },
},
{ $addToSet: { 'userHistory.coursesVisited': course } },
{ returnOriginal: false }
);
let updatedUserPageRef = updatedUser.value.userHistory.pagesVisited;
if (updatedUserPageRef) {
let pageFound = await updatedUserPageRef.findIndex(item => item.page === page);
if (pageFound >= 0) {
let updatedUserPage = await onlineUsersDB.findOneAndUpdate(
{'userID': updatedUser.value.userID},
// Here I want to reference the variable pageFound
{$inc: { 'userHistory.pagesVisited.[pageFound].timesVisited': 1 }},
{ returnOriginal: false }
);
console.log(JSON.stringify(updatedUserPage,null, 2));
}
}
let users = await onlineUsersDB.find({'online': true}).toArray();
io.to(room).emit('online-users', users);
io.to(room).emit('user-back-online', updatedUser);
} else {
if (userDetails.userID !== '100') {
await onlineUsersDB.insert(userDetails);
}
let users = await onlineUsersDB.find({'online': true}).toArray();
io.to(room).emit('online-users', users);
}
}).catch((e) => console.log(e));
In the above code where my comment is I want to reference the variable pageFound in my object dot notation like so:
{$inc: { 'userHistory.pagesVisited.[pageFound].timesVisited': 1 }}
It works when I give it a hardcoded value like:
{$inc: { 'userHistory.pagesVisited.0.timesVisited': 1 }}
After experimenting a little bit I made it to work like this:
I broke out my query string into another variable using template literals.
let pageInc = `userHistory.pagesVisited.${pageFound}.timesVisited`;
And then referenced the variable in my query like so:
{$inc: { [pageInc]: 1 }}
And it works now.

$addToSet Based on Object key exists

I have array 'pets': [{'fido': ['abc']} that is a embeded document. When I add a pet to the array, how can I check to see if that pet already exists? For instance, if I added fido again... how can I check if only fido exists and not add it? I was hoping I could use $addToSet but I only want to check part of the set(the pets name).
User.prototype.updatePetArray = function(userId, petName) {
userId = { _id: ObjectId(userId) };
return this.collection.findOneAndUpdate(userId,
{ $addToSet: { pets: { [petName]: [] } } },
{ returnOriginal: false,
maxTimeMS: QUERY_TIME });
Result of adding fido twice:
{u'lastErrorObject': {u'updatedExisting': True, u'n': 1}, u'ok': 1, u'value': {u'username': u'bob123', u'_id': u'56d5fc8381c9c28b3056f794', u'location': u'AT', u'pets': [{u'fido': []}]}}
{u'lastErrorObject': {u'updatedExisting': True, u'n': 1}, u'ok': 1, u'value': {u'username': u'bob123', u'_id': u'56d5fc8381c9c28b3056f794', u'location': u'AT', u'pets': [{u'fido': [u'abc']}, {u'fido': []}]}}
If there is always going to be "variable" content within each member of the "pets" array ( i.e petName as the key ) then $addToSet is not for you. At least not not at the array level where you are looking to apply it.
Instead you basically need an $exists test on the "key" of the document being contained in the array, then either $addToSet to the "contained" array of that matched key with the positional $ operator, or where the "key" was not matched then $push directly to the "pets" array, with the new inner content directly as the sole array member.
So if you can live with not returning the modified document, then "Bulk" operations are for you. In modern drivers with bulkWrite():
User.prototype.updatePetArray = function(userId, petName, content) {
var filter1 = { "_id": ObjectId(userId) },
filter2 = { "_id": ObjectId(userId) },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
// Return the promise that yields the BulkWriteResult of both calls
return this.collection.bulkWrite([
{ "updateOne": {
"filter": filter1,
"update": update1
}},
{ "updateOne": {
"filter": filter2,
"update": update2
}}
]);
};
If you must return the modified document, then you are going to need to resolve each call and return the one that actually matched something:
User.prototype.updatePetArray = function(userId, petName, content) {
var filter1 = { "_id": ObjectId(userId) },
filter2 = { "_id": ObjectId(userId) },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
// Return the promise that returns the result that matched and modified
return new Promise(function(resolve,reject) {
var operations = [
this.collection.findOneAndUpdate(filter1,update1,{ "returnOriginal": false}),
this.collection.findOneAndUpdate(filter2,update2,{ "returnOriginal": false})
];
// Promise.all runs both, and discard the null document
Promise.all(operations).then(function(result) {
resolve(result.filter(function(el) { return el.value != null } )[0].value);
},reject);
});
};
In either case this requires "two" update attempts where only "one" will actually succeed and modify the document, since only one of the $exists tests is going to be true.
So as an example of that first case, the "query" and "update" are resolving after interpolation as:
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets.fido": { "$exists": true }
},
{ "$addToSet": { "pets.$.fido": "ccc" } }
And the second update as:
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets.fido": { "$exists": false }
},
{ "$push": { "pets": { "fido": ["ccc"] } } }
Given varibles of:
userId = "56d7b759e955e2812c6c8c1b",
petName = "fido",
content = "ccc";
Personally I would not be naming keys like this, but rather change the structure to:
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets": [{ "name": "fido", "data": ["abc"] }]
}
That makes the update statements easier, and without the need for variable interpolation into the key names. For example:
{
"_id": ObjectId(userId),
"pets.name": petName
},
{ "$addToSet": { "pets.$.data": content } }
and:
{
"_id": ObjectId(userId),
"pets.name": { "$ne": petName }
},
{ "$push": { "pets": { "name": petName, "data": [content] } } }
Which feels a whole lot cleaner and can actually use an "index" for matching, which of course $exists simply cannot.
There is of course more overhead if using .findOneAndUpdate(), since this is afterall "two" actual calls to the server for which you need to await a response as opposed to the Bulk method which is just "one".
But if you need the returned document ( option is the default in the driver anyway ) then either do that or similarly await the Promise resolve from the .bulkWrite() and then fetch the document via .findOne() after completion. Albeit that doing it via .findOne() after the modification would not truly be "atomic" and could possibly return the document "after" another similar modification was made, and not only in the state of that particular change.
N.B Also assuming that apart from the keys of the subdocuments in "pets" as a "set" that your other intention for the array contained was adding to that "set" as well via the additional content supplied to the function. If you just wanted to overwrite a value, then just apply $set instead of $addToSet and similarly wrap as an array.
But it sounds reasonable that the former was what you were asking.
BTW. Please clean up by horrible setup code in this example for the query and update objects in your actual code :)
As a self contained listing to demonstrate:
var async = require('async'),
mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient;
MongoClient.connect('mongodb://localhost/test',function(err,db) {
var coll = db.collection('pettest');
var petName = "fido",
content = "bbb";
var filter1 = { "_id": 1 },
filter2 = { "_id": 1 },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
console.log(JSON.stringify(update1,undefined,2));
console.log(JSON.stringify(update2,undefined,2));
function CleanInsert(callback) {
async.series(
[
// Clean data
function(callback) {
coll.deleteMany({},callback);
},
// Insert sample
function(callback) {
coll.insert({ "_id": 1, "pets": [{ "fido": ["abc"] }] },callback);
}
],
callback
);
}
async.series(
[
CleanInsert,
// Modify Bulk
function(callback) {
coll.bulkWrite([
{ "updateOne": {
"filter": filter1,
"update": update1
}},
{ "updateOne": {
"filter": filter2,
"update": update2
}}
]).then(function(res) {
console.log(JSON.stringify(res,undefined,2));
coll.findOne({ "_id": 1 }).then(function(res) {
console.log(JSON.stringify(res,undefined,2));
callback();
});
},callback);
},
CleanInsert,
// Modify Promise all
function(callback) {
var operations = [
coll.findOneAndUpdate(filter1,update1,{ "returnOriginal": false }),
coll.findOneAndUpdate(filter2,update2,{ "returnOriginal": false })
];
Promise.all(operations).then(function(res) {
//console.log(JSON.stringify(res,undefined,2));
console.log(
JSON.stringify(
res.filter(function(el) { return el.value != null })[0].value
)
);
callback();
},callback);
}
],
function(err) {
if (err) throw err;
db.close();
}
);
});
And the output:
{
"$addToSet": {
"pets.$.fido": "bbb"
}
}
{
"$push": {
"pets": {
"fido": [
"bbb"
]
}
}
}
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 1,
"nModified": 1,
"nRemoved": 0,
"upserted": []
}
{
"_id": 1,
"pets": [
{
"fido": [
"abc",
"bbb"
]
}
]
}
{"_id":1,"pets":[{"fido":["abc","bbb"]}]}
Feel free to change to different values to see how different "sets" are applied.
Please try this one with string template, here is one example running under mongo shell
> var name = 'fido';
> var t = `pets.${name}`; \\ string temple, could parse name variable
> db.pets.find()
{ "_id" : ObjectId("56d7b5019ed174b9eae2b9c5"), "pets" : [ { "fido" : [ "abc" ]} ] }
With the following update command, it will not update it if the same pet name exists.
> db.pets.update({[t]: {$exists: false}}, {$addToSet: {pets: {[name]: []}}})
WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
If the pets document is
> db.pets.find()
{ "_id" : ObjectId("56d7b7149ed174b9eae2b9c6"), "pets" : [ { "fi" : [ "abc" ] } ] }
After update with
> db.pets.update({[t]: {$exists: false}}, {$addToSet: {pets: {[name]: []}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
The result shows add the pet name if it does Not exist.
> db.pets.find()
{ "_id" : ObjectId("56d7b7149ed174b9eae2b9c6"), "pets" : [ { "fi" : [ "abc" ] }, { "fido" : [ ] } ] }

Resources