How to increase counter on repetition of value under an array mongodb - arrays

I have a mongoose schema like this:
{
"_id" : ObjectId("5a7acda13b808dbed05d6505"),
"symbol" : "#AKS",
"counter" : 4
},
{
"_id" : ObjectId("5a7acda13b808dbed05d6506"),
"symbol" : "#AKD",
"counter" : 5
}
Now if I want to update multiple column i.e "symbol" values on one query then MongoDB updateMany Query will do. This is Query:
MyCollectionName.updateMany({ 'symbol' : { $in : req.body.array}}, { $inc : { 'counter': 1 } }, function(err, data) {
if(err) {
console.log('error')
} else {
console.log('value incremented')
}
});
By this Query If I give the Array value i.e
var array = ["#AKS", "#AKD"];
Then it will Increment the counter of Both. My Question is If I will Provide the Same value on an array then I want Two increment not one. Like this:
var array = ["#AKS", "#AKS"];
//I want the Increment of 2.
var array = [#AKS", "#AKS", "#AKS", "#AKS"]
//at this stage I want the counter of Increment is 4
But currently it will do Only One. Is this possible????
Any help is really appreciated.

You can use the bulkWrite API for this update as it allows you to compose an array of bulkWrite() write operations in which you can use the updateOne operation for each element in the array. The following example shows how you can apply it in your case:
let ops = [];
const handleResult = res => console.log(res);
const handleError = err => console.error(err);
req.body.array.forEach(function(item) {
ops.push({
"updateOne": {
"filter": { "symbol": item },
"update": { "$inc": { "counter": 1 } }
}
});
if (ops.length === 1000) {
MyCollectionName.bulkWrite(ops).then(handleResult).catch(handleError);
ops = [];
}
})
if (ops.length > 0) MyCollectionName.bulkWrite(ops).then(handleResult).catch(handleError);

what if instead of using updateMany you just forEach your array:
const yourArray = req.body.array;
yourArray.forEach(function(item) {
MyCollectionName.update({ 'symbol': { $in: item }}, { $inc: { 'counter': 1 }, function (err, data) {
// additional code
});
});

Related

How to find out number of elements in MongoDB array?

My collection of products consists of _id, product/title, product/price and reviews. Reviews is an array with all reviews for that specific product. I am trying to print out 10 products with the largest number of reviews but can't find the right solution.
This is what I tried so far:
var mapFunction = function() {
emit(this._id, {
product: this,
count_reviews: this.reviews.length
});
};
var reduceFunction = function(key, values) {
var count = 0;
var product;
values.forEach(function(value) {
count += value.count_reviews;
product = value.product
});
return { count_reviews: count, product: product};
};
db.products.mapReduce(mapFunction, reduceFunction, {
out: { inline: 1},
query: {},
sort: { count_reviews: -1 },
limit: 10
});
When I run this code it prints out 10 elements, but not with the largest number of reviews.
I wouldn't use mapReduce (unless you're on a version so old you have to)
I'd use an aggregation, if you have strict data - you can omit the $match, if you have "strictish" data (e.g., it's an array or missing) you can use a simpler match: { reviews: { $exists: true } }
db.products.aggregate([
{
$match: {
"value.reviews": { $type: "array" }
}
},
{
$set: { review_count: { $size: "$value.reviews" } }
},
{
$sort: { review_count: -1 },
},
{
$limit: 10
}
])

How can I realize "find" with using "$in" by two parameters in mangoose?

Hi I have the function which get object with id array like this.
{
"participants": [
"5fa566b5b96a9407c804ccd4",
"5fa5639cb96a9407c804ccce"
]
}
And I need to find object which have exactly this fields
Objects in which i try to find it looks like this:
{
"_id" : ObjectId("5fac288e53a6a72e94f721fa"),
"participants" : [
ObjectId("5fa566b5b96a9407c804ccd5"),
ObjectId("5fa5639cb96a9407c804ccc5")
],
"messages" : [],
}
I am trying to do like this but it doesn't work.
async filterDialog(newDialog: any){
const res = await Dialog.find({
'participants': { $in: { ...newDialog.participants } }, function(err: any, dialog: any) {
if (err) {
return err
} else {
return dialog
}
},
})
return res;
}
Will be glad if someone help me.
I think you need $all operator.
Check this query:
db.collection.find({
"participants": {
"$all": yourArray
}
})
Only return the document where the field participants contains all element from the given array.
Example here where I've used numbers instead of ObjectId to read easier.
Please check if this is the behavior you expect.

MongoDB : Complex Arrary field

{
"_id" : ObjectId("58d9084841a6168234689aee"),
"ID" : "01",
"data" : {
"Type1" : {
"value" : "ABC",
"timestamp" : "2017-03-20 16:01:01"
},
"Type2" : {
"value" : "ccc",
"timestamp" : "2017-03-20 16:01:01"
}
}
}
I want to get timestamp of each TYPE from mongodb using queryobject using nodejs.
How to get it,Please help.
var queryObject = url.parse(req.url,true).query;
var mdb = db.collection("HISTORY").find({{'timestamp':{"$gte":queryObject.fromdate,"$lt" : queryObject.todate}},{"ID":1});
Here is my node service:
function getHistory(req,res){
try{
var queryObject = url.parse(req.url,true).query;
var index=0, resultset = [];
var db1 = db.collection("HISTORY").find({$and : [{'data.Type1.timestamp':{"$gte": new Date(queryObject.fromdate),"$lt" : new Date(queryObject.todate)}},
{'data.Type2.timestamp':{"$gte": new Date(queryObject.fromdate),"$lt" : new Date(queryObject.todate)}},
{'data.Type3.timestamp':{"$gte": new Date(queryObject.fromdate),"$lt" : new Date(queryObject.todate)}},
{'data.Type4.timestamp':{"$gte": new Date(queryObject.fromdate),"$lt" : new Date(queryObject.todate)}},
{'data.Type5.timestamp':{"$gte": new Date(queryObject.fromdate),"$lt" : new Date(queryObject.todate)}}
]},{"Ino":1,"ID":1,"data":1});
db1.count(function(err, count) {
console.log("count" , count);
db1.each(function(err, doc) {
if(doc!=null){
var valdata=doc.alarms;
var fields = [];
var queryString ="SELECT field1,NAME FROM details c inner join locdetails l on c.loc_id=l.loc_id where no='"+doc.Ino+"' limit 1;";
var dtfield1 = null;
var dtfield2 = null;
connection.query(queryString, function(err,result){
index++;
if(err){
}else{
if(result.length>0)
{
dtfield1 = result[0].field1;
dtfield2 = result[0].NAME;
if(dtfield1!=null){
for (var x in valdata) {
var dt = new Date(valdata[x].timestamp).toISOString().replace(/T/, ' ').replace(/\..+/, '');
var compareDate = new Date(dt);
if(compareDate.getTime()>=fromDate.getTime()&&compareDate.getTime()<=toDate.getTime()){
resultset.push({"Name":dtfield1,"LName":dtfield2,"Ino":doc.Ino,"ID":doc.ID,"data":x,"datav":valdata[x].value,"Timestamp":valdata[x].timestamp});
}
if(index == count){
res.writeHead(200, {
'Content-Type': 'application/json'
});
res.write(JSON.stringify(resultset));
res.end();
}
}} }}
});
} else {
}
});
});
}
catch (err) {
console.log("Exception -- ",err);
}
}
I want data should filter based on timestamp and same can be display in UI and download the displayed data.And also filter should be in UI like current day data and based on time filter also.
If you want search base on both Type1.timestamp and Type2.timestamp, you should use $and. you can change $and with $or if one matching one of them is enough
db.collection("HISTORY").find({
$and : [{'data.Type1.timestamp':{"$gte": new Date(queryObject.fromdate),"$lt" : new Date(queryObject.todate)}},
{'data.Type2.timestamp':{"$gte": new Date(queryObject.fromdate),"$lt" : new Date(queryObject.todate)}}
]
},
{ID:1}, function (err, res){
});
You can use aggregation to meet your requirements.
For your initial list which will only show the latest data values of each object, you can use the following pipeline -
[
{
$project:{
_id:1,
ID:1,
data:{ $objectToArray: "$data" }
}
},
{
$unwind:"$data"
},
{
$sort:{
"data.v.timestamp":-1
}
},
{
$group:{
_id:{
_id:"$_id",
ID:"$ID"
},
data:{
$first:"$data"
}
}
},
{
$addFields:{
data:["$data"]
}
},
{
$addFields:{
data:{ $arrayToObject: "$data" }
}
}
]
The result of the aggregation should give you an array of objects like this -
{
"_id" : ObjectId("5bc469c11f8e482416d6edb3"),
"ID" : "01",
"data" : {
"Type2" : {
"value" : "ccc",
"timestamp" : ISODate("2017-03-22T16:01:01.000+06:00")
}
}
}
As for filtering the collection to get documents with data properties that have timestamp values in the given range, the above pipeline can be modified to acquire it
[
{
$project:{
_id:1,
ID:1,
data:{ $objectToArray: "$data" }
}
},
{
$unwind:"$data"
},
{
$sort:{
"data.v.timestamp":-1
}
},
{
$match:{
"data.v.timestamp":{ $gt: "start date value", $lt: "end date value"}
}
},
{
$group:{
_id:{
_id:"$_id",
ID:"$ID"
},
data:{
$addToSet:"$data"
}
}
},
{
$project:{
_id:"$_id._id",
ID:"$_id.ID",
data:{ $arrayToObject: "$data" }
}
}
]
The result of this aggregation will produce array of objects similar to the one described above. But this time each document's data object will only have properties that match the timestamp condition.
Hope this helps. A final word of advice to you would be to rethink the schema design of the history collection. Because if simplistic date based queries are getting this difficult for you. You can only imagine what the future might hold for you with this design.

How to compare 2 collections and fetch matched field data in mongodb?

I have a two collection as given below.
Collection 1: List
{
"_id" : ObjectId("5b618589d6edcc135c1aee0a"),
"name" : "ABC",
"special" : "Golmal",
"dim" : "2122",
}
Collection 2: Data
Document 1:
{
"_id" : ObjectId("5b6023d9589ff6bfb5dd75d7"),
"date" : "2018/08/13",
"special" : "Golmal",
}
Document 2:
{
"_id" : ObjectId("5b602447589ff6bfb5dd7606"),
"date" : "2018/08/13",
"special" : "Godman",
}
Here I want compare both the collection and if "special":"Golmal" of Collection 1 is present in Collection 2 documents I want to display/fetch all the related date from Collection 1 document.
I have tried aggregate & $lookup as below
getAvailableList(): Promise<any> {
return this._mongoUtility.Db
.then(db => {
let list= db.collection('list');
let data= db.collection('data');
list.aggregate([{$lookup:
{
from: data,
localField: "special",
foreignField: "special",
as: "matches"
}
},
{
$match: { "matches": { $ne: [{}] } }
}
]);
return Promise.resolve();
})
.catch(err => {
return Promise.reject({message: 'Not found', err: err});
});
}
But I'm getting null.
I have also tied by using .find & forEach as below
let abc = [];
list.find({}).toArray()
.then(arr => {
arr.forEach(obj1 => {
data.find({special: obj1.special}).toArray()
.then(secondArr => {
console.log(secondArr);
if (secondArr.length > 0) {
abc.push(obj1);
}
});
});
});
return Promise.resolve();
But no result. I need value in JSON format. As MongoDB dont have joins. Is there any ways to crack it?
Well, I changed my way of doing that query by not using forEach and its working for me.
I come across $in in find in mongoDB website. Here's the answer
return new Promise((res, rej) => {
this._mongoUtility.Db
.then(db => {
let list= db.collection('List');
let data= db.collection('Data');
data.find({}).toArray()
.then(arr => {
let maps = arr.map(data => data.special);
list.find({special: {$in: maps}}).toArray()
.then(secondArr => {
res(secondArr);
});
});
})
.catch(err => {
return rej({message: 'Not found', err: err});
});
});
As of MongoDB 3.2, joins become possible.
https://www.mongodb.com/blog/post/joins-and-other-aggregation-enhancements-coming-in-mongodb-3-2-part-1-of-3-introduction

Add array values into MongoDB where element is not in array

In MongoDB, this is the simplified structure of my account document:
{
"_id" : ObjectId("5a70a60ca7fbc476caea5e59"),
"templates" : [
{
"name" : "Password Reset",
"content" : "AAAAAAAA"
},
{
"name" : "Welcome Message",
"content" : "BBBBBB"
}
]
}
There's a similar default_templates collection
let accnt = await Account.findOne({ _id: req.account._id }, { templates: 1 });
let defaults = await DefaultTemplate.find({}).lean();
My goal is to find the missing templates under account and grab them from defaults. (a) I need to upsert templates if it doesn't exist in an account and (b) I don't want to update a template if it already exists in an account.
I've tried the following:
if (!accnt.templates || accnt.templates.length < defaults.length) {
const accountTemplates = _.filter(accnt.templates, 'name');
const templateNames = _.map(accountTemplates, 'name');
Account.update({ _id: req.account._id, 'templates.name' : { $nin: templateNames } },
{ '$push': { 'templates': { '$each' : defaults } } }, { 'upsert' : true },
function(err, result) {
Logger.error('error %o', err);
Logger.debug('result %o', result);
}
);
}
This succeeds at the upsert but it will enter all default templates even if there's a matching name in templateNames. I've verified that templateNames array is correct and I've also tried using $addToSet instead of $push, so I must not understand Mongo subdoc queries.
Any ideas on what I'm doing wrong?
Edit: I've gotten this to work by simply removing elements from the defaults array before updating, but I'd still like to know how this could be accomplished with Mongoose.
You can try with bulkWrite operation in mongodb
Account.bulkWrite(
req.body.accountTemplates.map((data) =>
({
updateOne: {
filter: { _id: req.account._id, 'templates.name' : { $ne: data.name } },
update: { $push: { templates: { $each : data } } },
upsert : true
}
})
)
})

Resources