Optimizing query that makes 3 API calls to the MongoDB server - mongodb

const user_schema = mongoose.Schema(
{
user_name: {
type: String,
required: true,
},
},
{
collection: `user`,
timestamps: true,
}
);
const test_schema = mongoose.Schema(
{
test_name: {
type: String,
required: true,
},
},
{
collection: `test`,
timestamps: true,
}
);
const score_schema = mongoose.Schema(
{
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: "user",
required: true,
},
test_id: {
type: mongoose.Schema.Types.ObjectId,
ref: "test",
required: true,
},
test_score: {
type: Number,
required: true,
},
},
{
collection: `score`,
timestamps: true,
}
);
query:
Given an array of user_id and an array of test_id, query the score model to find out the test scores.
To get the array of user_id, a set of conditions is given and the user model must be queried to find the set of users matching the conditions.
To get the array of test_id, a set of conditions is given and the test model must be queried to find the set of tests matching the conditions.
What needs to be done:
Make one query request to the MongoDB server to get the array of user_id.
Make a separate query request to the MongoDB server to get the array of test_id.
Make another query request to the MongoDB server to get the test scores:
db.getCollection("score").aggregate([
{$match: {$and: {user_id: {$in: array_of_user_id}, {test_id: {$in: array_of_test_id}}}}}
])
Is this the most optimal way to get the test scores? Is it possible to make just one request to the MongoDB server?

Related

How to filter documents using find method in mongoose based on the data from reference in documents?

I am working on e-commerce like app. I have orderItem Schema
const orderItemsSchema = mongoose.Schema(
{
order: {
type: mongoose.Schema.Types.ObjectId,
ref: 'OrderItems',
required: true,
},
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Products',
required: true,
},
quantity: {
type: Number,
default: 1,
},
subCost: {
type: Number,
required: true,
},
},
{
timestamps: true,
}
);
Where product schema has a field "owner" which is also a reference.
I am expecting to get orderItems based on owners of the products.
For Example: A owner want to check which products of him has been sold. So he will query orderItems to get his sold items.
I'm not an expert in mongoose, so maybe the syntax is not entirely correct:
// You get all products _id that owner currently sells
const yourOwnerObjectId = mongoose.Types.ObjectId(yourOwnerId); // Create the objectId from a string
const productsOwner = Products.find({owner: yourOwnerObjectId}).select({_id: 1})
// You get all orders that contains any of previous product _id
const orderWithProductsSold = OrderItems.find({_id: {$in: productsOwner}})
I'm not sure about what returns the first query regarding _id. Maybe you have to do some type of casting to ObjectId or whatever to perform the second query, but I think the idea is right.

How can I populate nested array objects in mongoDB or mongoose?

I have an orders collection where each order has the following shape:
{
"_id": "5252875356f64d6d28000001",
"lineItems": [
{ productId: 'prod_007', quantity: 3 },
{ productId: 'prod_003', quantity: 2 }
]
// other fields omitted
}
I also have a products collection, where each product contains a unique productId field.
How can I populate each lineItem.productId with a matching product from the products collection? Thanks! :)
EDIT: orderSchema and productSchema:
const orderSchema = new Schema({
checkoutId: {
type: String,
required: true,
},
customerId: {
type: String,
required: true,
},
lineItems: {
type: [itemSubSchema],
required: true,
},
});
const itemSubSchema = new Schema(
{
productId: {
type: String,
required: true,
},
quantity: {
type: Number,
required: true,
},
},
{ _id: false }
);
const productSchema = new Schema({
productId: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
imageURL: {
type: String,
required: true,
},
price: {
type: Number,
default: 0,
},
});
I don't know the exact output you want but I think this is what you are looking for:
The trick here is to use $lookup in an aggregation stage.
First $unwind to deconstruct the array and can merge each id with the other collection.
Then the $lookup itself. This is like a join in SQL. It merges the desired objects with same ids.
Then recreate the population using $mergeObjects to get properties from both collections.
And last re-group objects to get the array again.
db.orders.aggregate([
{
"$unwind": "$lineItems"
},
{
"$lookup": {
"from": "products",
"localField": "lineItems.productId",
"foreignField": "_id",
"as": "result"
}
},
{
"$set": {
"lineItems": {
"$mergeObjects": [
"$lineItems",
{
"$first": "$result"
}
]
}
}
},
{
"$group": {
"_id": "$_id",
"lineItems": {
"$push": "$lineItems"
}
}
}
])
Example here
With this query you have the same intial data but "filled" with the values from the other collection.
Edit: You can also avoid one stage, maybe it is clear with the $set stage but this example do the same as it merge the objects in the $group stage while pushing to the array.
You can use the Mongoose populate method either when you query your documents or as middleware. However, Mongoose only allows normal population on the _id field.
const itemSubSchema = new Schema({
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'productSchema',
}
});
const order = await orderSchema.find().populate('lineItems.$*.product');
// special populate syntax necessary for nested documents
Using middleware you would still need to reconfigure your item schema to save the _id from products. But this method would automatically call populate each time you query items:
itemSubSchema.pre('find', function(){
this.populate('product');
});
You could also declare your item schema within your order schema to reduce one layer of joining data:
const orderSchema = new Schema({
lineItems: [{
type: {
quantity: {type: Number, required: true},
product: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'productSchema',
}
},
required: true,
}]
});
const orders = orderSchema.find().populate('lineItems');

Query in command must target single shard key

I have an array of document ids using which I wish to delete the documents with the given id. The document id is also the shard key of the document. So I provided the following query for model.deleteMany(query)
query:
{ doc_id: { '$in': [ 'docid1', 'docid2' ] } }
I still get the error Query in command must target single shard key.
Is it possible to overcome this without looping through the array and deleting the docs one by one?
By matching any document in the collection with the _id field, you can make a query or delete command:
db.collection.deleteMany({ _id: { $exists: true }})
During the specification of the schema model in the code, a Shard Key (Partition Key) must be specified. We may execute operations such as save, update, and delete once it is provided
const mySchema = new Schema({
requestId: { type: String, required: true },
data: String,
documents: [{ docId: String, name: String, attachedBy: String }],
updatedBy: {
type: {
name: { type: String, required: true },
email: { type: String, required: true },
}, required: true
},
createdDate: { type: Date, required: true },
updatedDate: { type: Date },
}, { shardKey: { requestId: 1 } }
);

How join two collections in MongoDB using node js

I have searched for join two collection in MongoDB. I found populate. But it is not working for my scenario. I am using mongoose in node js. My schema are like below.
const CoordinateSchema = new mongoose.Schema({
activationId: {
type: mongoose.Schema.ObjectId,
ref: 'Activation'
},
mac: {
type: mongoose.SchemaTypes.String,
required: true,
set: toLower
},
t: { type: Date },
userId: {
type: mongoose.Schema.ObjectId,
ref: 'User'
}
});
const UserSchema = new mongoose.Schema({
email: {
type: mongoose.SchemaTypes.String,
required: true,
//unique: true,
set: toLower
},
mac: {
type: mongoose.SchemaTypes.String,
required: true,
unique: true,
set: toLower,
index: true
},
dob: {
type: mongoose.SchemaTypes.Date,
},
gender: { type: mongoose.SchemaTypes.String, set: toLower },
activations: [{
activationId: {
type: mongoose.Schema.ObjectId,
ref: 'Activation'
},
userType: { type: mongoose.SchemaTypes.String, set: toLower },
_id: false
}]
}
i have thousands of records for single activation in coordinates collection.
My query query requires to filter distinct mac from coordinates collection which matches userType in user collection.
If i use populate method & then apply filter on that it won't restrict fetching record count because of it query is taking so much time because it will return thousands of records.
I want to fetch only coordinates which match userType in user collection.
So far i haven't found any efficient method to join two collection & apply where condition on it.
I want to know efficient method to join two collection in mongodb & apply where condition on both collections.

Find a Document where value matched in either field using Mongoose Middleware

I have a list of account connections between source and target accounts so my schema looks like
var ConnectionRequestSchema = new Schema({
sourceAccountId: {
type: Schema.ObjectId,
ref: 'Account'
},
targetAccountId: {
type: Schema.ObjectId,
ref: 'Account'
},
status: {
type: String,
enum: ['pending', 'accept', 'decline'],
trim: true
}
});
I want to query all documents where the sourceAccountId or the targetAccountId are equal to the queried accountId.
I saw this link how-to-find-a-document-where-either-one-or-another-field-matches-a-value which is relevant for find a docouments using the stand find method in Mongo.
User.findOne({
$or: [
{first_name: name},
{last_name: name},
],
}, function(err, user) {
})
But I would like to do this using Mongoose Middleware and I'm not sure how I would construct this condition.
already you figured out the solution, but you have to make some changes in query
ConnectionRequest.find({
$or: [
{sourceAccountId: "5736eac90a39c2547cb9d911"},
{targetAccountId: "5736eac90a39c2547cb9d911"},
],
}, function(err, connection) {
console.log(connection)
})
then finally you will get the result is array of documents