MongoDB select first result from documents meeting $in - javascript

I have this query in loop:
const currentDatas = await Promise.all(nearestStations.map(async (ns: any) => {
return await this.stationCurrentDataRepo.findOne({
where: { stationId: parseInt(ns[0], 10) },
order: { date: 'DESC' },
});
}));
I want to optimize that to don't make hundreds queries and get the data in one query.
What I need is to get newest record (sort by date) for every stationId from array of ids ($in array of ids). I need all data from every found document meeting what I specified above.

In MongoDB this is done with aggregation pipeline and $group operator.

Related

How do I get a virtual in mongoose, If a property is deselected

lets say I have post Model and schema contains UserId , Title, Desc and likes array which takes userId's as ref
when I make a query I get a virtual property like this to find out num of like of a post would have
schema.virtual("numLikes").get(function () {
return this.likes.length;
});
But the problem is when I run the findById() Method I dont want to get likes from database because likes array would contain large list of users
const post = await Post.findById(postId).select("-likes -shares");
so how do I get Likes count without fetching likes array ?
I believe this can be done using aggregation, by using the $size operators in a projection:
const aggregate = Post.aggregate([
{ $match: {_id: postId}},
{ $project: {
numberOfLikes: { $size: "$likes" }
}
}
]);
https://docs.mongodb.com/manual/reference/operator/aggregation/size/
https://mongoosejs.com/docs/api/aggregate.html#aggregate_Aggregate-project

Updating Multiple Records with varying conditions in a single query

With Typeorm + NestJS + Postgres is there a way to update multiple records with varying conditions and varying values in a single query. Normally I could do
await getConnection()
.createQueryBuilder()
.update(Entity)
.set({ columnName: "newValue" })
.where({ id: In(1,2,3,4,5,6) })
.execute();
and this will update all entries with the specified ID. But in the case of having the following data structure
const array = [{id: 1, value: 'New Value For Record 1'},..., {id: 1000, value: 'New Value For Record 1000'}]
I could use for loop to update each single entry as below:
array1.forEach(rec => {
usersRepo.update(
{ id: rec.id },
{
columnName: rec.value
}
);
})
but this does not seem to be efficient and won't give good performance. Is there a way to do achieve multiple update on varying conditions with query builder.
You can achieve that we two queries
Get all the rows you need, basically a find would be enough, like this.
after that map the results array to do your changes
Last thing to do is to use save method, from Typeorm docs:
save - Saves a given entity or array of entities. If the entity already exists in the database, then it's updated. If the entity does not exist in the database yet, it's inserted.
const array = await yourRepo.find()
const newArr = array.map(el=> ({...el, value: 'New Value For Record'+el.id})
await yourRepo.save(newArr)
Cheers.

Mongoose - Find by array of ids, but retrieve limited results (1 for each id)

i have my model called "Conversations", and my model "Messages", right now i want to retrieve all conversations with the last Message attached (only 1 message per conversation), so i filtered the conversationids and i queried the messages, but i'm not able to get this messages (last messages) for each conversation, thanks in advance.
let conversations = await ConversationModel.find({});
const conversationIds = conversations.map(conversation => conversation._id)
// ConversationIds is basically ["conversation1", "conversation2", "conversation3"]
// Te problem is here, i want to attach the las message for each conversation, if i put limit(1)
// i will get 1 record for all query, but i want the last message record for each conversation.
MessageModel.find({ _id: { "$in" : conversationIds} }, ...);
From information gathered in comments; This is possible to achieve in a case where MessageModel documents contain a time-stamp to identify latest of them.
Idea: Filter messages based on conversationIds via $match, sort them by timestamp for the next stage where $group on conversation reference (lets say conversation_id) and pick latest of them by $first accumulator.
Aggregation Query: playground link
db.collection.aggregate([
{
$match: {
conversation_id: {
$in: conversationIds
}
}
},
{
$sort: {
timestamp: -1
}
},
{
$group: {
_id: "$conversation_id",
latest_doc: {
$first: "$$ROOT"
}
}
}
]);

Mongo update removing other keys in document

I am trying mongo update where one document key from a different collection is inserted into another collection.
CODE
// update user document with remove of otp and new state set.
updateOne = await db.collection(_collection).updateOne(
// search basis.
__docUpdateSearchBasis,
// updates.
__docUpdateBasis
)
RESULT
You need to make query like this:
updateOne = await db.collection(_collection).findOneAndUpdate(
//Condition
{
_id: req.user.id
},
//Update what you want
{
$set: {
key: value
}
});

Using $elemMatch in projection field - not filtering as desired

This schema:
var Order = new Schema({
name: String,
products: [{
product: {
type: Schema.Types.ObjectId,
ref: "Product"
},
qty: Number
}]
});
I would like to find and return the order with only the product that has the matching id. My attempt at $elemMatch have not been successful and seem to return the entire order with all products. I can use the position operator ($) but would like in this case to specifically use $elemMatch.
Note that each product in the order is unique (within that order)
This returns all products:
Order.find({ _id: req.params.id}, { 'products.product': {$elemMatch: { _id: req.params.product }}}, function(err, order) {
This returns empty:
Order.find({ _id: req.params.id}, { 'products': {$elemMatch: { 'product._id': req.params.product }}}, function(err, order) {
What is correct syntax? I would like the order returned with just one (matching) product.
In the products array, the product field directly contains the product's id, so the correct syntax would be:
Order.find(
{ _id: req.params.id},
{ products: {$elemMatch: { product: req.params.product }}},
function(err, order) { ... });
You can use query projection only, if the product in an order is always unique (there can't be more than 1 same product in 1 order). If same product can appear more than one in 1 order, you cannot use query projection since it will only return the first found product in products array.
Instead, use aggregation. Make use of $unwind and $match operator.
It will be something like this in mongo shell javascript.
db.orders.aggregate([
{$match: {_id: Order_id_being_search}},
{$unwind: "$products"},
{$match: {"products.product._id": Product_id_being_search }}
])
It will return the desired product (only the product), in a selected Order document. If you want to return the desired product from all Order, just omit the first $match.
Sorry, I am not used to using "REF" so I assume a manual reference using _id key on the code above. You can modify that part in the code. You can use the algo.
MongoDB uses aggregation not only to find aggregation value like sum, avg, max, etc, like relational ones. It also uses aggregation framework for dealing with subdocuments. Your case is a perfect example of the use case of $unwind operation in aggregation framework.

Categories