Mongodb update field inside object array with one condition - node.js

I have the following field inside my mongo documents
"offers" : [
{
"price" : 15,
"Date1" : 1661385600000.0,
"fullname" : "name 1",
"isDeleted" : false,
"createdDate" : 1660660090798.0,
"expired" : false
},
{
"price" : 40,
"Date1" : 1661388600000.0,
"fullname" : "name 2",
"isDeleted" : false,
"createdDate" : 1660660090788.0,
"expired" : false
}
],
And I want to map all the objects inside the array of all documents in the DB and compare the field createdDate with the current date, if this comparison is true, I want to change the expired field value.

What I did was use updateMany method and in the update param I used $exist $ne and $lt operators to make the logic.
And the code was the following:
let today = new Date();
let twoDayAgo = today.setDate(today.getDate()-2); //subtracting 2 days from today's date
let orderCriteria = {
orderStatus : "STATUS_THAT_I_WHANT_TO_FIND",
offers : { $exists: true, $ne: [] },//if find the order by status, proving that the key offers exist and is not empty
isDeleted : false,
"offers.createdDate" : { $lt: twoDayAgo } //compares the value with twoDaysAgo variable
};
let data2Updat = {
$set: {
"offers.$.expired" : true
}
}
let result = await DAOManager.updateMany( Model, orderCriteria, data2Updat, {} )

Related

Mongodb DB query to compare the data from two collections and get the results if data is same

I have two collections data like below . I need compare the two collections data and get the data if the data is same . Tried with below query but is it not giving any results .I am new to mongodb query ,Can anyone help me how to get the code key values if the data is same in both collections .The query is not returning any errors but the code values are not getting populated to new cllection
**Collection 1:**
{
"_id" : {
"value" : "12345"
},
"value" : {
"Identifier" : "12345",
"statu" : "simple",
"code" : {
"key" : "US",
"value" : "United State of America"
},
"Code" : {
"key" : "UK",
"value" : "London"
}
}
**Collection 2** :
{
"_id" : ObjectId("12345"),
"value" : {
"Identifier" : "12345",
"statu" : "simple",
"code" : {
"key" : "US",
"value" : "United State of America"
},
"Code" : {
"key" : "UK",
"value" : "London"
}
},
}
Mongo DB :
var identifiers = db.getSiblingDB("Datbase").getCollection("Collection1").find({
$or:[
{'code':{$exists:true}},
{'Code1':{$exists:true}},
]
}).toArray()
var bulk = db.getSiblingDB("Database2").getCollection("Collection2").initializeUnorderedBulkOp()
identifiers.forEach(Identifier =>{
db.getSiblingDB("Database2").getCollection("Collection2").aggregate([
{
$match:{
"Identifier":'$Identifier',
}
},
{
"$group" : {
"_id" : {
"Identifier":'$Identifier',
"key" : "$code.key",
"value" : "$code.value",
"key1" : "$code1.key1",
"value1" : "$code1.value2",
}
}
}, {
$merge:{
into:{
db:'Database',
coll:'Collection'
}
}
}
])
})
Using cursor get one by one items from 1st collection
For every item from 1st collection query 2nd collection for existense
If item does not exist or it's body is different (Use lodash.isEqual method) push them to some array or use replaceOne method to insert or replace document. (install lodash: npm i --save lodash)
const _ = require('lodash'); // import it at top
(async () => { // You can remove it if Your code already inside async scope
const source = db.getSiblingDB("Datbase").getCollection("Collection1");
const destination = db.getSiblingDB("Datbase").getCollection("Collection2");
const query = {$or:[
{'code':{$exists:true}},
{'Code1':{$exists:true}},
]};
const cursor = await source.find(query);
const differentItems = [];
// iterating source collection items
while(await cursor.hasNext()) {
const src = await cursor.next(); // source collectio inem
const queryDestination = {
"Identifier": src.Identifier, // extend Your equality constrains here
};
const dest = await destination.findOne(queryDestination);
// it may not exist || may be different (not equal)
const isDifferent = !dest || !(
_.isEqual(
_.omit(src, ['_id']),
_.omit(dest, ['_id'])
)
);
if (isDifferent) {
differentItems.push({src, dest});
/* or replace in db
await destination.replaceOne(
queryDestination,
_.omit(src, ['_id']),
{
upsert: true,
}
);
*/
}
}
console.log(`found ${differentItems.length} different documments`);
console.log(JSON.stringify(differentItems, null, 4));
)();

mongoose #set and $unset conditional

in my project i want to allow optional fields in a document, but i don't want to save any null values, i.e. i want to remove any field with null.
the values can be changed during run time.
i found how i can make a field null if the user didn't send data to update it (all the fields that the user send empty values for should get deleted).
if the user send firstName/lastName as an empty string in the form i want to remove them.
await Employee.findOneAndUpdate({ _id: employee._id },
{$set: {firstName: null, lastName: null, ...req.body.newEmployee}}, { new: true, upsert: false });
i tried to add an $unset option but i got an error
MongoError: Updating the path 'firstName' would create a conflict at 'firstName'
i thought about deleting it after the update (as a second command) but i can't find a way to tell/get all the null fields of the employee, and i can't get a fixed values to check because there are many combination for the null/not null values, especially if there will be more fields in the future.
i.e. i cant tell if (FN: null, LN: null or FN: "johny", LN: null etc..)
Update : In case if you need to keep some fields as is in existing document then try this, with this new code only the fields coming in request will either be updated or deleted doesn't alter other existing fields in document :
Your node.js code :
let removeObj = {}
Object.entries(req.body).forEach(([key, val]) => {
if (!val) { delete req[body][key]; removeObj[key] = '' };
})
let bulkArr = []
if (req.body) bulkArr.push({
updateOne: {
"filter": { "_id": ObjectId("5e02da86400289966e8ffa4f") },
"update": { $set: req.body }
}
})
if (Object.entries(removeObj).length > 0 && removeObj.constructor === Object) bulkArr.push({
updateOne: {
"filter": { "_id": ObjectId("5e02da86400289966e8ffa4f") },
"update": { $unset: removeObj }
}
})
// For mongoDB4.2 removeObj has to be an array of strings(field names)
let removeObj = []
Object.entries(req.body).forEach(([key, val]) => {
if (!val) { delete req[body][key]; removeObj.push(key) };
})
// After this, Similar to above you need to write code to exclude empty removeObj for 4.2 as well.
Query :: On mongoDB version >= 3.2 to < 4.2 .bulkWrite():
db.yourCollectionName.bulkWrite(bulkArr)
Query :: From mongoDB version 4.2 .updateOne accepts aggregation pipeline :
db.yourCollectionName.updateOne(
{ "_id": ObjectId("5e02da86400289966e8ffa4f") },
[
{
$set: req.body
},
{ $unset: removeObj }
]
)
Old Answer : You need to try .findOneAndReplace() , if you want entire doc to be replaced :
db.yourCollectionName.findOneAndReplace({_id: ObjectId("5e02da86400289966e8ffa4f")},inputObj, {returnNewDocument:true})
Your collection :
{
"_id" : ObjectId("5e02da86400289966e8ffa4f"),
"firstName" : "noName",
"lastName": 'noName',
"value" : 1.0,
"empDept": 'general',
"password":'something'
}
Your request object is like :
req.body = {
firstName: 'firstName',
lastName: null,
value : 1.0,
empDept: ''
}
your node.js code (Removes all falsy values ( "", 0, false, null, undefined )) :
let inputObj = Object.entries(req.body).reduce((a,[k,v]) => (v ? {...a, [k]:v} : a), {})
your collection after operation :
{
"_id" : ObjectId("5e02da86400289966e8ffa4f"),
"firstName" : "firstName",
"value" : 1.0,
}
your collection after operation as per updated answer :
{
"_id" : ObjectId("5e02da86400289966e8ffa4f"),
"firstName" : "firstName",
"value" : 1.0,
"password":'something'
}

How to compare array mongodb query

I am currently building a cart system on my mongodb ecommerce app. I need help on how to query and compare array.
here document of cart:
{
"_id" : ObjectId("5d0531e27c8fa1029017ea20"),
"user" : ObjectId("5d0371319315c715fc34b0b0"),
"active" : true,
"item" : [
{
"product" : ObjectId("5d013eb63a2bdd11a46c8dd3"),
"option" : [
{
"name" : "Ukuran",
"value" : "Biru"
}
],
"quantity" : 1
},
{
"product" : ObjectId("5d013eb63a2bdd11a46c8dd3"),
"option" : [
{
"name" : "Ukuran",
"value" : "Biru"
}
],
"quantity" : 1
}
],
"created_at" : ISODate("2019-06-15T17:58:58.762Z"),
"updated_at" : ISODate("2019-06-15T17:59:13.334Z"),
"__v" : 0
}
I want to compare object of item.option field, so my cart system is if cart on database have same object option i will add quantity, otherwise push new object to item.
so current I am not asking on how to implement my cart system, but I want to compare each item.option object
I've already tried this
const cart = await CartModel.find({
"item.option": option
})
and get error Error: Query filter must be an object, got an array
Solved by myself, after many experiment finally i combine $in and $elemMatch for compare each array of object
// this is will dynamic
const optionArray = [
{
"name": "Warna",
"value": "Biru"
},
{
"name": "Ukuran",
"value": "XL"
}
]
const compareOptionQuery = []
for (let i = 0; i < optionArray.length; i++) {
compareOptionQuery.push({
$elemMatch: {
...option[i]
}
})
}
const cart = await CartModel.aggregate([
{
$and: [
{
_id: cartId,
user: userId
},
{
'item.option': {
$in: [...compareOptionQuery]
}
}
]
}
])
The issue with your implementation is that in CartModel.find("item.option": option) your filter (first parameter) encounters an array instead of an object.
This is because you are trying to call for an object option from item, however option is as element of an array field item.
If you wish to access item from an array field, you must specify conditions on the elements in the array field item using {<array field>: {<operator1>: <value1>}} like so:
CartModel.find({
item: {$option: option}
})

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

How to get the max with a where condition in this situation Mongoose / Node

I am working with Mongoose, I have a collection that has documents like this
{
"_id" : 1,
"body" : "[{\"order_id\":\"647936\",\"order_datetime\":\"2015-12-02 11:10:00\"}]",
"user_info" : {
"contact_email" : "test#test.com",
"contact_phone" : "1234567",
},
"type" : "ORDERS",
"version" : 1
}
{
"_id" : 2,
"body" : "[{\"order_id\":\"647936\",\"order_datetime\":\"2015-12-02 11:10:00\"}]",
"user_info" : {
"contact_email" : "test#test.com",
"contact_phone" : "1234567",
},
"type" : "ORDERS",
"version" : 2
}
{
"_id" : 3,
"body" : "[{\"order_id\":\"647936\",\"order_datetime\":\"2015-12-02 11:10:00\"}]",
"user_info" : {
"contact_email" : "test#test.com",
"contact_phone" : "1234567",
},
"type" : "ORDERS",
"version" : 3
}
As you can see in body field you can see the order_id , so same order_id can be repeated in multiple in documents but the version will be different.
What I want is I want to search for the maximum version number for a
given order_id .
In my case it would be 3 .
I tried to use simple queries like
myCollection.aggregate([
{ "$match" : { "body.order_id" : 647936 } },
{ "$group": {
"_id" :"version",
"max": { "$max": "version" }
}}
] , function(err, data){
console.log(err);
console.log(data);
});
But the result is
null
[]
** Note that my mongoose connection is working fine and I can do some simple queries and results are OK.
Your data is the problem here since what seems to be intended as a structured document has been stored as a string:
// Bad bit
"body" : "[{\"order_id\":\"647936\",\"order_datetime\":\"2015-12-02 11:10:00\"}]",
Instead you would want this:
// Acutally data and not a string
"body" : [{ "order_id": "647936", "order_datetime": ISODate("2015-12-02 11:10:00.000Z" }],
With data like that, getting the latest version is a simple matter of ordering the results, without the overhead of .aggregate():
myCollection.find({ "body.order_id": "647936" })
.sort({ "version": -1 }).limit(1).exec(function(err,result) {
})
No need to aggregate and it's much faster than doing so, as you are just picking out the document with the latest (largest) version number.
In order to "fix" the data you can do something like this as a "one shot" execution in the shell:
var bulk = db.myCollection.initializeOrderedBulkOp(),
count = 0;
// query selects just those "body" elements that are currently a string
db.myCollection.find({ "body": { "$type": 2 } }).forEach(function(doc) {
var fixBody = JSON.parse(doc.body); // Just parse the string
fixBody.forEach(function(el) {
// Fix dates
el.order_datetime = new Date(
Date.parse(el.order_datetime.split(" ").join("T") + "Z")
);
});
// And queue an update to overwrite the "body" data
bulk.find({ "_id": doc._id }).updateOne({
"$set": { "body": fixBody }
});
count++;
// Send every 1000
if ( count % 1000 == 0 ) {
bulk.execute();
bulk = db.myCollection.initializeOrderedBulkOp(),
}
});
// Send any remaining batched
if ( count % 1000 != 0 )
bulk.execute();
You might also want to convert those "strings" other than the date to numeric values in a similar fashion and change appropriately in the query.

Resources