Mongoose select $ne in array - node.js

I am wondering how you would do a query where array._id != 'someid'.
Here is an example of why I would need to do this. A user wants to update their account email address. I need these to be unique as they use this to login. When they update the account I need to make sure the new email doesn't exist on another account, but don't give an error if it exists on their account already (they didn't change their email, just something else in their profile).
Below is the code I have tried using. It doesn't give any errors, but it always returns a count of 0 so an error is never created even if it should.
Schemas.Client.count({ _id: client._id, 'customers.email': email, 'customers._id': { $ne: customerID } }, function (err, count) {
if (err) { return next(err); }
if (count) {
// it exists
}
});
I'm guessing it should use either $ne, or $not, but I can't find any examples for this online with an ObjectId.
Example client data:
{
_id: ObjectId,
customers: [{
_id: ObjectId,
email: String
}]
}

With your existing query, the customers.email and customers._id parts of your query are evaluated over all elements of customers as a group, so it won't match a doc that has any element with customerID, regardless of its email. However, you can use $elemMatch to change this behavior so the two parts operate in tandem on each element at a time:
Schemas.Client.count({
_id: client._id,
customers: { $elemMatch: { email: email, _id: { $ne: customerID } } }
}, function (err, count) {
if (err) { return next(err); }
if (count) {
// it exists
}
});

I was able to do this using aggregate.
Why this didn't work the way I had it: When looking for $ne: customerID it will never return a result because that _id does in fact exist. It can't combine cutomers.email and customers._id the way I wanted it to.
Here is how it looks:
Schemas.Client.aggregate([
{ $match: { _id: client._id } },
{ $unwind: '$customers' },
{ $match: {
'customers._id': { $ne: customerID },
'customers.email': req.body.email
}},
{ $group: {
_id: '$_id',
customers: { $push: '$customers' }
}}
], function (err, results) {
if (err) { return next(err); }
if (results.length && results[0].customers && results[0].customers.length) {
// exists
}
});
);

Related

Conditionally update item or delete item in one query in mongoose

So, what I'm trying to achieve here is two things. First one is to decrement the quantity of a product. Second is that if that product quantity is 0 then delete that product.
So I'm trying to set a condition that if product quantity is 0 then delete that product otherwise simply decrement the quantity.
Here is my cart document:
And here is my function:
const cart = await Cart.findOneAndUpdate(
{ userID: req.body.userID, "items.productID": req.body.productID },
{$lt: ['items.productID', 0] ? { $pull: { items: { productID: req.body.productID } } } : { $inc: { 'items.$.quantity': -1 } }},
{ new: true }
);
I have tried to achieve that with ternary operator but it didn't worked. Can anybody help me with this please? Thank you.
You cannot use a ternary operator in your query. Not only is this not supported in MongoDB, it will also simply be evaluated by node.js, not by MongoDB itself. In fact, conditional update operations of the nature you're describing are themselves not something that MongoDB is capable of handling.
Your best bet is to perform two queries, where the first one performs the decrement operation and the second pulls any items out that have a quantity less than or equal to 0:
const cartAfterDecrement = await Cart.findOneAndUpdate(
{ userID: req.body.userID, "items.productID": req.body.productID },
{ $inc: { 'items.$.quantity': -1 } },
{ new: true }
);
const cartAfterPull = await Cart.findOneAndUpdate(
{ userID: req.body.userID, items: { $elemMatch: {
productID: req.body.productID,
quantity: { $lte: 0 }
} } },
{ $pull: {items: { quantity: { $lte: 0 } } } },
{ new: true }
);
const cart = cartAfterPull ? cartAfterPull : cartAfterDecrement;
If you wish to allow products with quantities equal to 0, then modifying the second query is a trivial matter.
The only downside to this approach is that there will be a natural, very slight delay between these two updates, which may result in a very rare case of a race condition occurring where a cart is returned and contains an item with a quantity less than or equal to 0. In order to avoid this, you will need to either filter these values out with server code or by retrieving your cart(s) using aggregation, or you will need to make use of transactions to ensure that the two operations occur in sequence without the document being retrieved between the two write operations.
With that said, constructing the appropriate array filtering logic or transaction management is beyond the scope of this question, so will not be included in this answer.
Query
you can do it with a pipeline update ( >= MongoDB 4.2)
find the user that has that productID
reduce the items, to an array with item but
if productID not the one i look for, keep it as it is
else if quantity=0 remove it(don't add it to the reduced array)
else keep it with quantity-1
*findAndModify accepts pipeline also, for nodejs driver you must find the method that wraps this command, or run the command directly
Test code here
update(
{"userID": req.body.userID,
"items.productID": req.body.productID},
[{"$set":
{"items":
{"$reduce":
{"input":"$items",
"initialValue":[],
"in":
{"$switch":
{"branches":
[{"case":{"$ne":["$$this.productID", req.body.productID]},
"then":{"$concatArrays":["$$value", ["$$this"]]}},
{"case":{"$gt":["$$this.quantity", 0]},
"then":
{"$concatArrays":
["$$value",
[{"$mergeObjects":
["$$this", {"$subtract":["$this.quantity", 1]}]}]]}}],
"default":"$$value"}}}}}}])
Thanks #B. Fleming and all others for your answer. I have just use a simple code implementation for that however it wasn't what I was expected but still it's good to go.
module.exports.deleteCartItem = async (req, res, next) => {
var condition;
var update;
const product = await Cart.findOne({ userID: req.body.userID,
items: {
$elemMatch: {
productID: req.body.productID,
quantity: { $lte: 0 }
}
}
});
// IF PRODUCT WITH 0 QUANTITY PULL/DELETE THAT PRODUCT
if (product !== null) {
condition = { userID: req.body.userID, items: { $elemMatch: { productID: req.body.productID, quantity: { $lte: 0 } } } },
update = { $pull: {items: { quantity: { $lte: 0 } } } }
}
// OTHERWISE DECREMENT THE QUANTITY FOR THAT PRODUCT
else {
condition = { userID: req.body.userID, "items.productID": req.body.productID }
update = { $inc: { 'items.$.quantity': -1 } }
}
try {
const cart = await Cart.findOneAndUpdate(condition, update, { new: true });
if (cart !== null) {
res.status(200).json({
statusCode: 1,
message: "Item Removed",
responseData: cart
});
} else {
res.json({
statusCode: 0,
msgCode: 462,
message: 'Not found',
responseData: null
});
}
} catch (err) {
console.log('Server Error: ', err);
next(new Error('Server Error, Something was wrong!'));
}
}

Mongodb findone document in array by object id

Can I get only 1 photo by objectid? I only need to get 1 Image detail from 1 post by photo but what i get is all photo of post.
this is my db structure
and this is my code looks like:
Post.findOne({
$and: [
{ photo: { $elemMatch: { _id: id } } } ]
}).exec((err, post) => {
if (err) {
return res.status(400).json({ error: err });
}
req.post = post;
console.log(req.post);
next();
});
what i get in req.post is only [].
Thanks in advance
The $elemMatch projection operator provides a way to alter the returned documents, in here coupled with find utilizing second parameter which is projection will achieve that.
Post.find(
{},
{
_id: 0,
photo: { $elemMatch: { _id: id } }
}
);
This will provide leaned documents with the promise: .lean().exec():
Post.find(
{},
{
_id: 0,
photo: { $elemMatch: { _id: id } }
}
)
.lean()
.exec();
NOTE: $elemMatch behaviour is to return the first document where photo's id matches.
You can try with aggregate instead of findOne:
https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
Post.aggregate([
{ $match: { 'photo._id': id } },
{ $unwind: "$photo" },
{ $match: { 'photo._id': id } },
]);
Maybe not the best, but single photo data is achievable.

How can I sort then limit an array of results in mongoose?

I have a user collection with tons of properties for each user. One property is an array ('guestlist_submitted') and I am trying to return the most recent 3 items in that array according to the date field.
Using Mongoose, I am able to target that specific property on the user object, but I can't seem to limit or sort them.
I've tried using slice and and a date calculation to return the most recent 3 entires but I am struggling to get it right and not sure whats wrong.
I've tried using Sort/Limit on query builder but I've learned that these dont apply to the array inside the property I am targeting despite me saying " select('guestlist_submitted') as part ofthe query builder.
When I return the result after just targeting the property on the user object, it comes as an array with an object inside and another array inside that object. I am having problems traversing down it to target that inner array to use the slice and date sort.
First try
Users.
find({ fbid: req.query.fbid}).
select('guestlist_submitted -_id').
sort({"guestlist_submitted.date": 'desc'}).
limit(3).
exec((err, result) => {
if (err) throw (err));
if (result) {
res.send(result);
}
Second try:
Users.
find({ fbid: req.query.fbid}).
select('guestlist_submitted -_id').
exec((err, result) => {
if (err) res.send(JSON.stringify(err));
if (result) {
const slicedArr = result.guestlist_submitted.slice(0,1);
const sortedResultArr = slicedArr.sort(function(a,b){return new Date(b.date) - new Date(a.date); });
const newResultArr = sortedResultArr.slice(0, 2);
res.send(newResultArr);
}
The best I can get is 'guestlist_submitted' and the entire array, not sorted and its inside an object inside an array.
I expected the output to be the 3 most recent results from the guestlist_submitted array inside a new array that I can display on the client.
Thank you in advance for your help
EDIT - FIGURED OUT A SOLUTION
I ended up figuring out how to get what I wanted using aggregate()
Users.aggregate([
{
$match: {fbid: req.query.fbid}
},
{
$unwind: "$guestlist_submitted"
},
{
$sort: {'guestlist_submitted.date': -1}
},
{
$limit: 2
},
{
$group: {_id: '$_id', guestlist_submitted: {$push: '$guestlist_submitted'}}
},
{
$project: { guestlist_submitted: 1, _id: 0 }
}
]).exec((err, result) => {
if (err) res.send(JSON.stringify(err));
if (result) {
console.log(result);
res.send(result);
}
});
I ended up figuring out how to get what I wanted using aggregate()
Users.aggregate([
{
$match: {fbid: req.query.fbid}
},
{
$unwind: "$guestlist_submitted"
},
{
$sort: {'guestlist_submitted.date': -1}
},
{
$limit: 2
},
{
$group: {_id: '$_id', guestlist_submitted: {$push: '$guestlist_submitted'}}
},
{
$project: { guestlist_submitted: 1, _id: 0 }
}
]).exec((err, result) => {
if (err) res.send(JSON.stringify(err));
if (result) {
console.log(result);
res.send(result);
}
});

Node Mongo - find multiple parameters

I'm trying to find in a collection if there is already a session number, to avoid duplications. dadosORS.email and dadosORS.sessao (which is 3)come from a form. So when I do this:
mongoClient.collection('registosORS', function(err,collection){
collection.find({email:{$eq:dadosORS.email}},{sessao:{$eq:dadosORS.sessao}}).toArray(function(err,result){
try{
console.log(result);
}catch (err){
console.log(err);
}
if(result){
// callback(false)
return
} else {
I get result = undefined. If I change the query to
collection.find({email:dadosORS.email},{sessao:dadosORS.sessao}).toArray(function(err,result){
it lists my every occurence of the email:
[ { _id: 5a37b4c3da53ff1e825f94b4, sessao: '1' },
{ _id: 5a37b4e6da53ff1e825f94b6, sessao: '1' },
{ _id: 5a37b57ce500ca1ea5522e22, sessao: '2' } ]
So, how can I see if the dadosORS.sessao for that dadosORS.email already exists?
Just do an and query:
collection.find( { email : dadosORS.email, sessao : dadosORS.sessao } )
or can be expressed as
collection.find( { $and: [ { email : dadosORS.email }, { sessao : dadosORS.sessao } ] } )

Mongo Find is not working

This is my user schema
var UserSchema = new Schema({
Pcard: [{ type: Schema.Types.ObjectId, ref: 'Pcard' }]
})
These are id's saved in user Pcard array
"user": { // id 59560bc83e1fdc2cb8e73236
"Pcard": [
"595b43d16e4b7305e5b40845",
"595b459a6e4b7305e5b40848",
"595f48f58117c85e041f1e1c",
],
}
This is my Pcard Scema
var PcardSchema = new Schema({
Time : {
type : Date,
default: Date.now
},
})
I want to find user having Id and which also contains some id in Pcard array
User.find({ _id: req.user._id,
Pcard:{$in : [req.params.PcardId] }
}, function (err, Userpresent) {
if (err) {
res.json(code.Parked);
}
if (Userpresent === null || Userpresent === undefined) {
res.json(code.notAllowed);
}else{
This else is execting everytime.
}
}
});
when i querying with user which does not have a Pcardid in Pcard array it is still going in else condition !
for eg . i am querying with this id 59560bc83e1fdc2cb8e73236 and this not contain 5957bd177e996b56d08b991a in Pcard array but still it is going on else part of the user query.
If you are expecting only one user, use findOne.
Might be useful/cleaner for you to return early instead of having a bunch if-else.
User.findOne({
_id: req.user._id,
Pcard: { $in : [req.params.PcardId] }
}, function (err, user) {
if (err) {
return res.json(code.Parked);
}
// simplified: undefined and null are both falseys
if (!user) {
return res.json(code.notAllowed);
}
// user should be found at this point
});
Good read: All falsey values in JavaScript

Resources