MongoDB collection find method doesn't work not in order - node.js

I am trying to get some db collection depends on users ID.
const chat = await Chat.findOne({ users });
now this will work : "users": ["630200a45d22133dbe5bec44", "630200975d22133dbe5bec41"]
but this will not work: "users": [630200975d22133dbe5bec41", "630200a45d22133dbe5bec44"]
Same id's, just not in the right order.

You are looking for an exact match, so order matters. It seems what you want to be doing is to use $all, this checks all that the elements present in the input array exists in the db.
Additional you'll want to add a size check if you want to limit it so an exact match, otherwise documents like {"users": ["630200a45d22133dbe5bec44", "630200975d22133dbe5bec41", "otherid"] } will be matched.
Overall like so:
const chat = await Chat.findOne({
users: {
$all: [
"630200975d22133dbe5bec41",
"630200a45d22133dbe5bec44"
]
},
"users.2": {
$exists: false
}
})
Mongo Playground
Or dynamically based on input size:
const input = [
"630200975d22133dbe5bec41",
"630200a45d22133dbe5bec44"
];
const sizeKey = `users.${input.length}`
const chat = await Chat.findOne({
users: {
$all: input
},
[sizeKey]: {
$exists: false
}
})

Related

How to search for records by using a value in object array from mongoose?

I want to search for records using the below-mentioned Field and code I have used, returning nothing. I'm sure I'm querying wrongly, and can anyone help me build the correct query code?
Code I have used:
const { id } = req.params;
const requests = await Request.find({
itemsList: { item: id },
});
Return:
console.log(requests);
requests: []
const requests = await Request.find({
"itemsList.item": id,
});
will return the entire array if there is a match.
Playground
And make sure your data on the query part is matching the type.
In the image, you have a string. Use ObjectId if it is ObjectId.
Please try this
const requests = await Request.find({
itemsList: { $elemMatch: { item: mongoose.Types.ObjectId(id) }}},
});

how to loop over an array and get data for each element? - with mongoose & node.js

I am trying to build a social network. I have a collection of "connections"/"friend requests".
the schema is as follows:
{
senderId: { type: String },
receiverId: { type: String },
approved: { type: Boolean },
},
when the person didn't approve the connection yet, the connection is marked with "pending".
when the user enter to the website, i get all the pending connections people sent him, with this command:
const requestsPending = await Connection.find({
receiverId: userId,
approved: false,
});
the thing is, in the backend, I want to get the details of the people who send the friend requests (so I can show their name+picture).
I want to loop over the pending requests array, and get the extra data of the user who sent the request.
await User.findById(requestsPending[0][senderId]);
any idea how to do it for each element? what's the best approach?
or any idea how to do it more efficiently?
thank you.
this seems to work:
var requestsPendingWithUsersData = await Promise.all(
requestsPending.map(async (item) => {
const userData = await User.findById(item.senderId);
return {
item,
senderData: { picture: userData.picture, name: userData.username },
};
})
);
await User.find({
'senderId': { $in: [
101,102,103,...
]}
})
You can try something like the above, where you pass an array of senderID to $in. The $in clause is similar to like operator in SQL.
If the senderId is an ObjectId, pass them as ObjectID types
await User.find({
'senderId': { $in: [
mongoose.Types.ObjectId(<senderID>),
]}
})
If the mongo Document is heavier/larger, use lean() at the end of the query. Enabling the lean option tells Mongoose to skip instantiating a full Mongoose document.
await User.find({
'senderId': { $in: [
101,102,103,...
]}
}).lean()

mongodb multiple find by multiple conditions and one result nodejs

i have a collection for users transactions. i want to use a query by three id of users to get last transaction for each one. and i don't want to use a loop to do a query per user.
i used this:
const items = await db
.collection("transactions")
.find({ user: users[1] , user: users[2], user: users[3] })
.limit(3)
.sort({ $natural: -1 })
.toArray();
but it doesn't contain one result per condition because i know i'm doing it wrong.
i use:
const MongoClient = require("mongodb").MongoClient;
how should i do that?
thanks.
You probably need to do an aggregation using $group and $last.
https://docs.mongodb.com/manual/reference/operator/aggregation/group/#examples
db.collection("transactions").aggregate( [
{
$group: {
_id: user,
txnId: { $last: "$_id" }
}
}
] ).toArray();
This answer might also help you: https://stackoverflow.com/a/32134655/1742298

Using updateOne method to update an object field inside array - throws error "Cannot create field 'url' in element"

I have MongoDB database (with Mongoose) containing a collection of Products (among others), which looks like this:
[
{
name: 'Product A',
url: 'product-a',
category: 'accesory',
price: 12,
shortDescription: ['example description'],
technicalSpecs: [{ speed: 10, weight: 20 }],
images: [],
reviews: [],
relatedProducts: [
{
url: 'product-b',
name: 'Product B',
// to be added in Update query
//id: id_of_related_product
}
]
} /* other Product objects */
]
As every MongoDB document is provided with _id property by default, but within the relatedProducts array i only have url and name properties, i want to add the id property (associated with corresponding Product) for each object in the relatedProducts array, so i will be able to conveniently query and process those related products.
I came up with an idea to query all Products to get only those, which have non-empty relatedProducts array. Then i loop them and i search for Product model, which has specific url and name properties - this let's me get it's true (added by MongoDB) _id. At the end i want to add this _id to matching object inside relatedProducts array.
My code:
async function assignIDsToRelatedProducts(/* Model constructor */ Product) {
const productsWithRelatedOnes = await Product.find(
{ relatedProducts: { $ne: [] }}, ['relatedProducts', 'name', 'url']
);
for (const productItem of productsWithRelatedOnes) {
for (const relatedProduct of productItem.relatedProducts) {
const product = await Product.findOne(
{ url: relatedProduct.url, name: relatedProduct.name },
'_id'
);
// throws error
await productItem.updateOne(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': product._id } }
);
}
}
}
However it throws the following error:
MongoError: Cannot create field 'url' in element {relatedProducts: [ /* array's objects here */ ]}
I don't know why MongoDB tries to create field 'url', as i use it to project/query url field (not create it) in updateOne method. How to fix this?
And - as i am newbie to MongoDB - is there a simpler way of achieving my goal? I feel that those two nested for..of loops are unnecessary, or even preceding creation of productsWithRelatedOnes variable is.
Is it possible to do with Mongoose Virtuals? I have tried it, but i couldn't match virtual property within the same Product Model - attach it to each object in relatedProducts array - after calling .execPopulate i received either an empty array or undefined (i am aware i should post at-the-time code of using Virtual, but for now i switched to above solution).
Although i didn't find solution or even reason of my problem, i solved it with a slightly other approach:
async function assignIDsToRelatedProducts(Product) {
const productsHavingRelatedProducts = Product.find({ relatedProducts: { $ne: [] }});
for await (const withRelated of productsHavingRelatedProducts) {
for (const relatedProductToUpdate of withRelated.relatedProducts) {
const relatedProduct = await Product
.findOne(
{ url: relatedProductToUpdate.url, name: relatedProductToUpdate.name },
['url', '_id']
);
await Product.updateMany(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': relatedProduct._id } }
);
}
}
const amountOfAllProducts = await Product.find({}).countDocuments();
const amountOfRelatedProductsWithID = await Product
.find({ 'relatedProducts.id': { $exists: true } }).countDocuments();
console.log('All done?', amountOfAllProducts === amountOfRelatedProductsWithID);
}
Yet, i still suppose it can be done more concisely, without the initial looping. Hopefully somebody will suggest better solution. :)

What is the best way to query data conditionally in MongoDB (node.js)?

What I'm basically trying to do, is to render different look on the front-end (react.js) based on whether the user is part of the group or not. I tried conditional querying, looping on front-end etc.
What would be your approach to tackle this problem?
My last try was an aggregation, but it doesn't return any value:
Role.aggregate(
[
{
$project: {_id: roleID,
UserInRole: { $cond: {
if:{ userList: { $in: [userID]}}, then: true, else: false} }}
}
]
)
To come up with a functioning MongoDB query that determines whether a user is part of a group requires an understanding of how you're structuring your database and groups collection. One way to structure that is like so:
{
"_id" : ObjectId("594ea5bc4be3b65eeb8705d8"),
"group_name": "...",
"group_members": [
{
"user_id": ObjectId("<same one from users collection"),
"user_name": "Alice",
"user_profile_picture": "<link_to_imag_url>"
},
{
"user_id": ObjectId("<same one from users collection"),
"user_name": "Bob",
"user_profile_picture": "<link_to_imag_url>"
},
....
]
}
Your group document/object can have attributes for things like it's name, creation date, description, etc. One of the attributes should be "group_members" which can be used when doing your query to see if a user (based on id) is part of a specific group.
The MongoDB $elemMatch operator seems like a great choice to satisfy your use case (if you're using a similar group data structure to example one. Further down on that $elemMatch page is a section on Array of Embedded Documents. You can do a query like:
db.groups.find({
_id: ObjectId("<id of group you're checking"),
group_members: {
$elemMatch: { user_id: ObjectId("<user id of user you're checking>") }
}
})
That will return either 1 or 0 results. 1 if there is a group with that _id and a group_members array that contains an element that has the user id specified, otherwise 0.
Now to use that in Node, you can use the MongoDB NodeJS Driver in conjunction with an Express web server:
var MongoClient = require('mongodb').MongoClient
var ObjectID = require('mongodb').ObjectID;
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
// Connection URL
var url = 'mongodb://localhost:27017/test'; // change test to whichever db you added the collections to
app.get('/partOfGroup', (req, res) => {
if (req.query.groupId == null || req.query.userId == null) {
return res.send('Must include both groupId and userId')
} else {
MongoClient.connect(url, function(err, db) {
var collection = db.collection('groups');
collection.findOne({
_id: ObjectID(req.query.groupId),
group_members: {
$elemMatch: { user_id: req.query.userId}
}
}, function(err, result) {
return res.send(result != null)
})
})
}
});
app.listen(3000, function () {
console.log('Example app listening on port 3000');
});
With that up and running, you can go to the url http://localhost:3000/partOfGroup?groupId=594ea5bc4be3b65eeb8705d8&userId=12345 and it should return true or false depending on if there's a group with id 594ea5bc4be3b65eeb8705d8 and user with id 12345 in that group.
From your front-end code, make a request to that url when a logged in user visits a group page, replacing the group id and user id appropriately. The response you get will determine whether to display a "Join" or "Leave" button.

Resources