Check if document exists in mongodb - mongodb

This is how I check if a document exists:
var query = {};
if (req.body.id) {
query._id = {
$ne: new require('mongodb').ObjectID.createFromHexString(req.body.id)
};
}
Creditor.native(function(err, collection) {
collection.find({
$or: [{
name: req.body.name
}, {
sapId: req.body.sapId
},
query
]
}).limit(-1).toArray(function(err, creditors) {
if (creditors.length > 0) {
return res.send(JSON.stringify({
'message': 'creditor_exists'
}), 409);
} else {
return next();
}
})
});
To avoid that multiple documents exist with the same name or/and the same sapID I do this check on every creation/update of a document.
E.g. I want to update this document and give it a different name
{
name: 'Foo',
sapId: 123456,
id: '541ab60f07a955f447a315e4'
}
But when I log the creditors variable I get this:
[{
_id: 541a89a9bcf55f5a45c6b648,
name: 'Bar',
sapId: 3454345
}]
But the query should only match the same sapID/name. However there totally not the same. Is my query wrong?

You're currently finding docs where name matches OR sapId matches OR _id doesn't match. So that last clause is what's pulling in the doc you're seeing.
You probably mean to find docs where (name matches OR sapId matches) AND _id doesn't match.
collection.find({ $and: [
query,
{ $or: [{
name: req.body.name
}, {
sapId: req.body.sapId
}
] } ]
})
Or more simply:
collection.find({
_id: { $ne: require('mongodb').ObjectID.createFromHexString(req.body.id) },
$or: [{
name: req.body.name
}, {
sapId: req.body.sapId
}
]
})

Related

Update or append to a subcollection in mongo

I have a collection containing a subcollection. In one request, I would like to update a record in the subcollection or append to it if a match doesn't exist. For a bonus point I would also like this update to be a merge rather than an overwrite.
A crude example:
// Schema
{
subColl: [
{
name: String,
value: Number,
other: Number,
},
];
}
// Existing record
{
_id : 123,
subColl: [
{name: 'John',
value: 10,
other: 20}
]
}
// example
const update = { _id: 123, name: 'John', other: 1000 };
const { _id, name, other } = update;
const doc = await Schema.findById(_id);
const idx = doc.subColl.findIndex(({ name: nameInDoc }) => nameInDoc === name);
if (idx >= 0) {
doc.subColl[idx] = { ...doc.subColl[idx], other };
} else {
doc.subColl.push({ name, other });
}
doc.save();
Currently I can achieve this result by pulling the record, and doing the update/append manually but I am assuming that achieving it with a pure mongo query would be much faster.
I have tried:
Schema.findOneAndUpdate(
{ _id: 123, 'subColl.name': 'John' },
{ $set: { 'subColl.$': [{ name: 'John', other: 1000 }] } }
)
but this won't handle the append behaviour and also doesn't merge the object with the existing record, rather it overwrites it completely.
I am not sure is there any straight way to do this in single query,
Update with aggregation pipeline starting from MongoDB v4.2,
$cond to check name is in subColl array,
true condition, need to merge with existing object, $map to iterate loop, check condition if matches condition then merge new data object with current object using $mergeObjects
false condition, need to concat arrays, current subColl array and new object using $concatArrays
const _id = 123;
const update = { name: 'John', other: 1000 };
Schema.findOneAndUpdate(
{ _id: _id },
[{
$set: {
subColl: {
$cond: [
{ $in: [update.name, "$subColl.name"] },
{
$map: {
input: "$subColl",
in: {
$cond: [
{ $eq: ["$$this.name", update.name] },
{ $mergeObjects: ["$$this", update] },
"$$this"
]
}
}
},
{ $concatArrays: ["$subColl", [update]] }
]
}
}
}]
)
Playground

delete element out of array with $pull and $cond operators

I want to pull elements out of the array only if some condition is met
This is my document structure:
{
_id: "userId",
posts: [{
_id: "postId",
comments:[{
_id: "commentId",
userid: "some id of an user" // USER
},{
_id: "commentId2",
userid: "some id of an user2"
}]
}]
}
I want to delete the element from the comments array with the given commentId. This should be done only if userid is USER. If that condition isn't met, that means that comment doesn't belongs to the user that wants to delete it so I decline it.
Tried Attempt :
Post.findOneAndUpdate(
{
_id: mongoose.Types.ObjectId(userId)
},
{
$pull: {
$cond: [
{
"posts.$[post].comments.$[comment].userid": {
$eq: USER
}
},
{
$pull: {
comments: {
_id: mongoose.Types.ObjectId(commentId)
}
}
}
]
}
},
{
arrayFilters: [
{
"comment._id": mongoose.Types.ObjectId(commentId)
},
{
"post._id": mongoose.Types.ObjectId(postId)
}
]
}
)
That code above doesn't work, I'm stuck there & I don't know how to continue. maybe somebody knows how to fix this.
You can try below query :
Post.findOneAndUpdate(
{
_id: mongoose.Types.ObjectId(userId) // Fetches actual document
},
// Any matching object that has these fields/values in comments array will be pulled out
{
$pull: {"posts.$[post].comments": { _id : mongoose.Types.ObjectId(commentId), "userid": USER }}},
{
arrayFilters: [
{
"post._id": mongoose.Types.ObjectId(postId) // Checks which object inside `posts` array needs to be updated
}
]
}
)
Note : Use an option { new : true } in mongoose to return updated document, or in shell use { returnNewDocument : true }

MongoDB Remove or Limit fields conditional aggregation

I am having some issue writing a find/aggregate mongo query where my requirement is to get all the documents but having condition like:
Suppose I have 2 documents:
{
_id: 5ccaa76939d95d395791efd2,
name: 'John Doe',
email: 'john.doe#foobar.com',
private: true
}
{
_id: 5ccaa76939d95d395791efd2,
name: 'Jane Doe',
email: 'jane.doe#foobar.com',
private: false
}
Now the query I am trying to get my head around is if the field private is true then when I query I must get all documents except email fields not included if private is true, like this:
{
_id: 5ccaa76939d95d395791efd2,
name: 'John Doe',
private: true
}
{
_id: 5ccaa76939d95d395791efd2,
name: 'Jane Doe',
email: 'jane.doe#foobar.com',
private: false
}
Tried $redact, $cond, $$PRUNE, $$DESCEND in aggregate() as well as came across $$REMOVE (looks like it is newest feature) but unable to get the required output. Please help me out with the Query
You can use $$REMOVE to remove a field from returned documents.
db.collection.aggregate([
{ "$addFields": {
"email": {
"$cond": [
{ "$eq": ["$private", true] },
"$$REMOVE",
"$email"
]
}
}}
])
MongoPlayground
Thank you Anthony Winzlet, his solution worked like a charm.
If anyone faces same problem and requires to include more than 1 fields, I am doing so by writing this method:
function condition(privateFieldLimitationArray, publicFieldLimitationArray) {
const condition = {};
privateFieldLimitationArray.map((d, i) => {
condition[d] = {
"$cond": [
{ "$eq": ["$private", true] },
"$$REMOVE",
publicFieldLimitationArray.includes(d) ? '$$REMOVE' : '$'+d
]
}
});
return condition;
}
Then, you can use the above function like:
const privateLimitationArray = ['updatedAt', 'createdAt', 'email', 'lname', 'friendslist', '__v'];
const publicLimitationArray = ['updatedAt', 'createdAt', '__v'];
YourModel.aggregate([
{
$match: {
// Your query to find by
}
}, {
"$addFields": condition(privateLimitationArray, publicLimitationArray)
}
])
.then(result => {
// handle result
})
.catch(error => {
// handle error
});

mongo $or to return a single document base don first critera

I have the following query:
db.getCollection('MyCollection').find({
$or: [{
"Zips": {
$elemMatch: { "ZipCode5": "95757" , "ZipCode4": "6237"}
}
}, {
"Zips": {
$elemMatch: { "ZipCode5": "95757" , "ZipCode4": "0000"}
}
}]
})
I have both documents on my collection, but I want only to return the document that matches the first criteria if both exist, and the 2nd if the first dosn't exist.
Currently, the above query returns both if they both exist.
Why not just limit to first row then?..
db.getCollection('MyCollection').find({
$or: [{
"Zips": {
$elemMatch: { "ZipCode5": "95757" , "ZipCode4": "6237"}
}
}, {
"Zips": {
$elemMatch: { "ZipCode5": "95757" , "ZipCode4": "0000"}
}
}]
}).limit(1)

Mongoose pull nested array

My user schema is
{
widgets: [
{
commands: [
{
name: 'delete'
}
]
}
],
name: 'John',
}
and I want to delete widgets.commands by id. I use mongoose. I know the id but when I make the pull request it doesn't delete it.
$pull: {widgets.$.commands: {_id: req.params.id}} Any suggestions?
Here you go
update({}, {
$pull: {
'widgets.commands._id': req.params.id,
},
});
$pull mongoDB documentation
#example from doc
db.profiles.update( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )