Remove multiple documents from mongo in a single query - mongodb

I have a list of mongo '_id' which I want to delete. Currently I am doing this
# inactive_users --> list of inactive users
for item in inactive_users:
db.users.remove({'_id' : item})
but my problem is the list is too huge... (it might go 100,000 +). So querying for every item in list will only increase the load on server. Is their a way to pass the entire list in mongo query so that I dont have to fire query again and again.
Thank you

db.users.deleteMany({'_id':{'$in':inactive_users}})

List them all and use $in operator:
db.users.remove({_id:{$in:[id1, id2, id3, ... ]}})

You need to pass the ids in a specific format using ObjectId():
db.users.remove({_id: {$in: [ObjectId('Item1'), ObjectId('Item2'), ObjectId('Item2')]}});
Remove doesn't accept integer - you have to use ObjectId instance with _id format as a string.

var collection = db.users;
var usersDelete = [];
var ObjectID = req.mongo.ObjectID; //req is request from express
req.body.forEach(function(item){ //req.body => [{'_id' : ".." , "name" : "john"}]
usersDelete.push(new ObjectID(item._id));
});
collection.remove({'_id':{'$in': usersDelete}},function(){
//res.json(contatos);
});

I had the same question and ran across these answers but it seems the MongoDB manual is recommending deleteMany instead of remove. deleteMany returns the delete count as well as an acknowledgement of the write concern (if the operation succeeded).
const ids = [id1, id2, id3...];
const query = { _id: { $in: ids} };
dbo.collection("users").deleteMany(query, function (err, obj) {
if (err) throw err;
});
Or with an arrow function:
const ids = [id1, id2, id3...];
const query = { _id: { $in: ids} };
dbo.collection("users").deleteMany(query, (err, obj) => {
if (err) throw err;
});
Or better yet, with a promise:
const ids = [id1, id2, id3...];
const query = { _id: { $in: ids} };
dbo.collection("users").deleteMany(query)
.then(result => {
console.log("Records Deleted");
console.log(JSON.stringify(result));
//for number removed...
console.log("Removed: " + result["n"]);
})
.catch(err => {
console.log("Error");
console.log(err);
});

Related

Mongoose: deleteOne middleware for cascading delete not working

With remove being deprecated in Mongoose 5.7.13, I want to use deleteOne instead. I need to get the id of the deleted document so that I can then delete further related documents in other collections in a cascade. I thought that "this" within the context of the pre middleware hook was meant to refer to the removed document, but instead it's just an empty object. Is there a canonical working example of this? I'm still currently using 5.7.12 at this point - will that make a difference here?
Here is the code I'm currently working with. The issue is that I can't get the projectId at the start because the reference is completely empty. Doing this on post rather than pre, or switching the option to run on query rather than document all yield the same result.
ProjectSchema.pre("deleteOne", {document:true}, (next) => {
const projectId = this._id;
ListModel.find({parentProject:projectId}, (err, lists) => {
if(err){
console.log("error cascading project delete to lists", {err});
}
lists.map(list => {
ListModel.deleteOne({_id:list._id}, (err, result) => {
if(err) {
console.log("error on project delete cascade", {err});
}
});
});
});
});
It depends whether you call deleteOne on document or on model. The later just have no document to bind it to.
The former gives you the document as you expect:
const project = await ProjectModel.findOne();
project.deleteOne();
The later gives you the Query. There is no _id in the query, but it has this.op for example, which in this middleware will be "deleteOne":
await ProjectModel.deleteOne();
The only way to get the document id in this case is to ensure it is provided in the query:
await ProjectModel.deleteOne({_id: "alex"});
Then you can get it in the middleware from the filter:
const projectId = this.getFilter()["_id"]
You can specify query: false in second parameter of the middleware to ensure the it is not invoked when you call deleteOne on model. So the best you can do:
ProjectSchema.pre("deleteOne", {document:true, query: false}, (next) => {
const projectId = this._id;
....
});
ProjectSchema.pre("deleteOne", {document:false, query: true}, (next) => {
const projectId = this.getFilter()["_id"];
if (typeof projectId === "undefined") {
// no way to make cascade deletion since there is no _id
// in the delete query
// I would throw an exception, but it's up to you how to deal with it
// to ensure data integrity
}
});
Please take a look at corresponding tests on v5.7.12: https://github.com/Automattic/mongoose/blob/5.7.12/test/model.middleware.test.js#L436
In the mongoose docs it says "Model.deleteOne() does not trigger pre('remove') or post('remove') hooks."
There is solution if you can refactor your delete operations with findByIdAndDelete, it triggers the findOneAndDelete middleware,
So we can add this middleware to Project Schema.
Project model:
const mongoose = require("mongoose");
const ProjectChild = require("./projectChild");
const ProjectSchema = new mongoose.Schema({
name: String
});
ProjectSchema.post("findOneAndDelete", async function(doc) {
console.log(doc);
if (doc) {
const deleteResult = await ProjectChild.deleteMany({
parentProject: doc._id
});
console.log("Child delete result: ", deleteResult);
}
});
module.exports = mongoose.model("Project", ProjectSchema);
ProjectChild model:
const mongoose = require("mongoose");
const projectChildSchema = new mongoose.Schema({
name: String,
parentProject: {
type: mongoose.Schema.Types.ObjectId,
ref: "Project"
}
});
module.exports = mongoose.model("ProjectChild", projectChildSchema);
I created a project like this:
{
"_id": "5dea699cb10c442260245abf",
"name": "Project 1",
"__v": 0
}
And created 2 project child for this project:
Child 1
{
"_id": "5dea69c7b10c442260245ac0",
"name": "Child 1 (project 1)",
"parentProject": "5dea699cb10c442260245abf",
"__v": 0
}
Child 2
{
"_id": "5dea69e8b10c442260245ac1",
"name": "Child 2 (project 1)",
"parentProject": "5dea699cb10c442260245abf",
"__v": 0
}
I created a sample route to delete a project by its id like this:
router.delete("/project/:id", async (req, res) => {
const result = await Project.findByIdAndDelete(req.params.id);
res.send(result);
});
When I send a DELETE request to this route, we see the following info in the console:
console.log(doc);
{ _id: 5dea699cb10c442260245abf, name: 'Project 1', __v: 0 }
console.log("Child delete result: ", deleteResult);
Child delete result: { n: 2, ok: 1, deletedCount: 2 }
So we could deleted the 2 children of the project, when we deleted the project.
As an alternative you can also use findOneAndRemove, it triggers findOneAndRemove post middleware.
So in the ProjectSchema we replace the post middleware like this:
ProjectSchema.post("findOneAndRemove", async function(doc) {
console.log(doc);
if (doc) {
const deleteResult = await ProjectChild.deleteMany({
parentProject: doc._id
});
console.log("Child delete result: ", deleteResult);
}
});
When we use a findOneAndRemove operation, the result will be the same as the first alternative:
const result = await Project.findOneAndRemove({ _id: req.params.id });

Mongoose: MongoError: >1 field while trying to project out $elemMatch

I'm trying to project out only the matched element of an array, in the updated version. But I'm getting the error: "MongoError: >1 field in obj: { _id: 0, lotes.$: 1 }"
If I remove 'new: true', it works. But then I have the doc before the update. And I would really like the updated version.
What's wrong? How can I fix it?
The Offer doc is something like:
{
_id
series: [ Serie ]
}
Serie structure is something like:
{
_id
public.available: Number
public.expDate: Date
}
I'm using Mongoose:
var query = {
'_id': offerId,
'series': {
$elemMatch: {
'_id': serieId,
'public.available': {$gt:0},
'public.expDate': {$gt: now}
}
}
};
var update = {
$inc: { 'series.$.public.available' : -1 }
};
var options = { // project out just the element found, updated
new:true,
select: {
'_id': 0,
'series.$': 1
}
};
Offers.findOneAndUpdate(query, update, options)
.then( element => {
...
}
For anyone else experiencing this error, it is also the most common error when trying to perform an illegal action such as trying to update a database element inside of a findOne request.
Making sure your request is correct, such as findOneAndUpdate should be your first port of call when you get this error.
As Anthony Winzlet pointed out in the links, there seems to be an issue with Mongoose, in which if you use 'new:true', you can't project out the $elemMatch.
So my solution was to keep using 'new:true' only, without projections. And reduce the array later on to get the $elemMatch:
.then( (result) => {
var aux = result.series.reduce((acu, serie, index) => {
if (serie._id == req.params.serieId) return index;
});
var element = result.series[aux];
}

What is the syntax for a mongoose query where I just want one property's value?

I am sending a query to mongoDB using mongoose. The collection is named Step. I want the result of this query to be an array of _id values, one per step. Currently I am getting all of the step objects in their entirety, because req.query isn't defined in this case.
service:
this.getSteps = function() {
return $http({
method: 'GET',
url: '/api/step'
})
.then(function(response) {
return response.data;
});
};
controller:
readStep: function (req, res) {
Step.find(req.query, function(err, result) {
if (err) {
res.status(500).send(err);
}
res.status(200).send(result);
});
}
Set the second parameter of the find query to '_id' to retrieve only the _id of the objects.
Step.find(req.query, '_id', function(err, result) {
This will return data like this:
[{_id: 123}, {_id: 234}]
If you want to get an array of the Step ids on their own, use the javascript map function like so
result = result.map(function(doc) {
return doc._id;
});
which will give you an array like this:
[123, 234]
You'll need to use query.select, something like as shown below:
Step.find(query).select({ "_id": 1}).then(....);
I'm not able to type much because I'm responding from my handheld.
Hope this help!

Is there a way to query MongoDB Rest API with a list or array of IDs

I'm using a MEAN stack and with Mongoose. Is there a way to query MongoDB with multiple ids to only return those specific IDs in one query e.g. /api/products/5001,5002,5003
Is this possible or would I need to query each product individually or add an additional attribute to the products and query by that.
Update: To clarify as suggested below I've managed to get it partially working using {'_id': { $in: [5001,5002,5003]} however I'm having problems figuring out how to pass the list from the api url to the find function.
Using Express.js for router
router.get('/list/:ids', controller.showByIDs);
exports.showByIDs = function(req, res) {
Product.find({'_id': { $in: [req.params.ids]}}, function (err, product) {
if(err) { return handleError(res, err); }
if(!product) { return res.send(404); }
return res.json(product);
})
};
Then trying /api/products/list/5001 works however /api/products/list/5001,5002 doesn't. I'm not sure if it's a syntax problem in the url or my router code that needs to change or the controller.
You can use the $in operator to query for multiple values at once:
Products.find({_id: {$in: [5001, 5002, 5003]}}, function (err, products) { ... });
On the Express side, you need to use a format for the ids parameter that lets you split it into an array of id values, like you had in your first example:
/api/products/5001,5002,5003
Then in your route handler, you can call the split function on the req.params.ids string to turn it into an array of id values that you can use with $in:
exports.showByIDs = function(req, res) {
var ids = req.params.ids.split(',');
Product.find({'_id': { $in: ids}}, function (err, product) {
if(err) { return handleError(res, err); }
if(!product) { return res.send(404); }
return res.json(product);
})
};

findAndModify query not executing in callback to aggregation

I have an aggregation query on a students collection that is returning two sets of results
for each student like this
{ _id: 1543,
name: 'Bill Jackson',
scores: { type: 'homework', score: 38.86823689842918 } }
{ _id: 1543,
name: 'Bill Jackson',
scores: { type: 'homework', score: 15.861613903793295 } }
That's working fine. Now in the callback I want to remove one of the scores for each student. I use ugly nested conditionals below to isolate which of the two records I want to remove, and, once that's achieved I create a find and Modify query to remove the doc but there's no evidence of it getting run. Neither the error or success callback to the findAndModify are getting run, however I am able to log that I'm inside the area where the findAndModify is getting called.
Is it possible to query the db in the callback to an aggregation? If not, how should I perform an operation that persists in the db?
//aggregation query ommitted
, function(err, result) { //callbackstarts here with result of aggregation query that returns two records for each student
for (var i=0; i<result.length; i++) {
var id = result[i]['_id'];
if (id === result[i]['_id']){
if (foo && foo === result[i]['_id']){
//if we're in here, we know we need to remove score associated with this result[i]['_id']
//create findAndModify to remove the record
var query = { '_id' : result[i]['_id']}
var sort = []
var operation = { '$pull' : { 'scores.score' : result[i]['scores']['score'] } };
var options = []
console.log('this code is getting called but findAndModify not')
db.collection('students').findAndModify(query, sort, operation, options,function(err, doc) {
if(err) throw err;
if (!doc) {
console.log("record not found");
}
else {
console.log("changed doc" + doc);
}
});
}else {
var foo = result[i]['_id'] //part of logic to isolate which of two records to remove
}