Mongoose complex query for search - mongodb

This is my document, where user register and create their store and in each store user can add books now anonymous user want to fetch the book by id or all the books available in system
[
{
_id: ObjectId("61c0a1a895a761d0fe1a1408"),
storeName: 'Rose Book Store',
ownerName: 'Rose Ahmed',
books: [
{
name: 'economics',
totalStock: 12,
stockStatus: true,
_id: ObjectId("61c20d9fcfef88e51e37bdaa")
}
],
createdAt: ISODate("2021-12-21T17:02:30.221Z"),
__v: 0
},
{
_id: ObjectId("61c17c1481adc44ea10c6083"),
storeName: 'Toufik Book Store',
ownerName: 'Toufik Ahmed',
books: [
{
name: 'Database',
totalStock: 14,
stockStatus: true,
_id: ObjectId("61c217b72e2dc3395071bb26")
},
{
name: 'Database',
totalStock: 14,
stockStatus: true,
_id: ObjectId("61c217e92e2dc3395071bb2b")
},
{
name: 'MySql',
totalStock: 0,
stockStatus: false,
_id: ObjectId("61c218142e2dc3395071bb31")
}
],
createdAt: ISODate("2021-12-21T18:03:13.296Z"),
__v: 0
}
]
how can i get all books, expected output
books:[{
name:"Some_book",
totalStock:12,
stockStatus: true,
_id: "Some_id"
},
{
name:"Some_other_book"
totalStock:0,
stockStatus:false
_id: "Some_id"
}]
how can i get book by id when user enters any valid book _id
expected output in given below
books:{
name:"some_book",
totalStock: 2,
_id: "some object_id"}

Here i am sharing normal aggregation query https://mongoplayground.net/p/SOO_Ffia5-q
where we are just grouping all books into one document so you get all the books.
Same aggregation can be used to get the matching book based on id. https://mongoplayground.net/p/ILHMW-kPueD
in aggregation we used below stages:
match: filter book based on _id of book
unwind: unwind matched document as matched document may have other nonmatched booked in same user/store
match: filter unwinded books so we just get matched book
project: to just return book and not all fields
Using this sample queries you can easily create your mongoose equivalent code,

Related

MongoDB query filter

I have a collection Restaurant, Products, and Reviews
restaurants: [
{
_id: 1,
name: "Burger King",
location: {
type: "Point",
coordinates: [
11.111,
11.111
]
},
isOpen: true
},
{
_id: 2,
name: "McDonald's",
location: {
type: "Point",
coordinates: [
22.222,
22.222
]
},
isOpen: true
},
{
_id: 3,
name: "Chick-fil-A",
location: {
type: "Point",
coordinates: [
33.333,
33.333
]
},
isOpen: true
}
],
products: [
{
_id: 1,
name: "Breakfast Whopper Jr.",
price: "$1.29",
isAvailable: true,
isApproved: true,
quantitySold: 50,
restaurant: ObjectId("1")
},
{
_id: 2,
name: "Big Mac",
price: "$4.35",
isAvailable: true,
isApproved: true,
quantitySold: 59,
restaurant: ObjectId("2")
},
{
_id: 3,
name: "Spicy Chicken Sandwich",
price: "$3.29",
isAvailable: true,
isApproved: true,
quantitySold: 60,
restaurant: ObjectId("3")
},
{
_id: 4,
name: "Chicken Sandwich",
price: "$2.29",
isAvailable: true,
isApproved: true,
quantitySold: 58,
restaurant: ObjectId("3")
}
],
reviews: [
{
_id: 1,
message: "Big burger even if it's junior size.",
restaurant: ObjectId("1"),
product: ObjectId("1")
},
{
_id: 2,
message: "Big Mac is really the best burger in town.",
restaurant: ObjectId("2"),
product: ObjectId("2")
},
{
_id: 3,
message: "Spicy Chicken Sandwich rocks!",
restaurant: ObjectId("3"),
product: ObjectId("3")
},
{
_id: 4,
message: "Chicken Sandwich is the best sandwich of Chick-fil-A!",
restaurant: ObjectId("3"),
product: ObjectId("4")
},
{
_id: 5,
message: "Chicken Sandwich is the best!",
restaurant: ObjectId("3"),
product: ObjectId("4")
}
]
My implementation
db.products.aggregate([
{
$lookup: {
"from": "restaurant",
"localField": "restaurant",
"foreignField": "_id",
"as": "restaurant"
}
},
{
$match: {
"restaurant.isOpen": true,
"isApproved": true,
"isAvailable": true
}
},
{
$project: {
"restaurant.isOpen": 1,
"isApproved": 1,
"isAvailable": 1,
"restaurant.location": 1,
"quantitySold": 1
}
},
{
$match: {
"restaurant.location": {
$geoWithin: {
$centerSphere: [[222.22, 222.33], 10/6378.1] // sample coordinates
}
}
}
},
{
$sort: {
// best seller
"quantitySold": -1
}
}
In my implementation. I'm currently getting the 10km of restaurant that isOpen: true and its products that isAvailable: true and isApproved: true. I also sorted the best selling by the quantitySold field.
Now, I want to query also the most reviewed products and I want to show only 1 product per restaurant in my app based on best seller and most reviewed. Then, after that the remaining products will randomize below the sorted best seller and most reviewed.
Example. I'm in 10km of Burger King, MCDO, Chick-fil-A and I will see their products based on best seller and most reviewed (1 product). Then after, I will see all of their products randomize below.
Currently you have OPEN (restaurant), APPROVED and AVAILABLE list of items.
(You have to adjust the existing query's $project section to get few more fields accordingly).
To get the reviews you can use $lookup, where
{
$lookup:
{
from: "reviews",
localField: "_id",
foreignField: "product",
as: "product_reviews"
}
}
you will get product_reviews array, for each product. You can then perform $groupby and $count to get the total count per product in the next pipeline stage.
After getting the above list in the aggregation pipeline use GROUP BY as follows
{ $group : {
_id : "$restaurant_id",
items: { $push : "$$ROOT" }
}}
(Get restaurant id and name for products in $project stage in existing query). Now you have a list with restaurants id/names and their product arrays.
You can refer to below links to know more about $$ROOT
Mongo group and push: pushing all fields
As your list is already sorted you will have the best seller item at top.
Regarding the other food items do you really need to randomize every time? you can then try to work with $sample (refer :https://docs.mongodb.com/manual/reference/operator/aggregation/sample/)
or follow this mongo db aggregate randomize ( shuffle ) results as per your need.

How to query multiple collections in mongodb (without using $lookup)?

I would like to create a single query that gets data from three different collections from providing a single query parameter. I have seen methods that use $lookup but I do not want to use that as I cannot use it on sharded collections.
Here is an example to explain further.
I have three collections: user, chatroom and chatMessage.
user collection:
{
_id: ObjectId('456'),
username: 'John',
contacts: [
{
_id: ObjectId('AB12'),
name: 'Mary',
idOfContact: ObjectId('123'),
},
{
_id: ObjectId('AB34'),
name: 'Jane',
_idOfContact: ObjectId('234'),
},
{
_id: ObjectId('AB56'),
name: 'Peter',
_idOfContact: ObjectId('345'),
}
],
}
chatroom collection:
{
_id: ObjectId('AB34'),
usersInThisChatRoom: [
ObjectId("456"),
ObjectId("123"),
ObjectId("234"),
]
}
chatMessage collection:
[
{
_id: ObjectId("M01"),
chatRoomObjectId: _id: ObjectId('AB34'),
senderObjectId: ObjectId('456'),
message: 'Hello humans!',
date: ISODate("2019-09-03T07:24:28.742Z"),
},
...(other messages)
]
What I would like to be returned
[
{
chatRoomObjectId: ObjectId('AB34'),
usersInThisChatRoom: [
{
contactName: 'John',
contactUserId: ObjectId('456'),
},
contactName: 'Mary',
contactUserId: ObjectId('123'),
},
contactName: 'Jane',
contactUserId: ObjectId('234'),
}
]
chatMessages: [
{
_id: ObjectId("M01"),
senderObjectId: ObjectId('456'),
message: 'Hello humans!',
date: ISODate("2019-09-03T07:24:28.742Z"),
},
...(other messages)
]
},
...(other documents)
]
Is there a way to get my desired results by making a single query using the user._id and will that be performance friendly?
Or, do I have to make several queries, one after another, to achieve what I want?
According to this answer, you cannot perform a single query across multiple collections (aside from the $lookup aggregation pipeline function.
This means that You either use the $lookup aggregation pipeline or you make several queries to the DB.

How to only return X amount of embedded documents with MongoDB?

I have a large collection called posts, like so:
[{
_id: 349348jf49rk,
user: frje93u45t,
comments: [{
_id: fks9272ewt
user: 49wnf93hr9,
comment: "Hello world"
}, {
_id: j3924je93h
user: 49wnf93hr9,
comment: "Heya"
}, {
_id: 30283jt9dj
user: dje394ifjef,
comment: "Text"
}, {
_id: dkw9278467
user: fgsgrt245,
comment: "Hola"
}, {
_id: 4irt8ej4gt
user: 49wnf93hr9,
comment: "Test"
}]
}]
My comments subdocument can sometimes be 100s of documents long. My question is, how can I return just the 3 newest documents (based on the ID) instead of all the documents, and return the length of all documents as totalNumberOfComments as a count instead? I need to do this for 100s of posts sometimes. This is what the final result would look like:
[{
_id: 349348jf49rk,
user: frje93u45t,
totalNumberOfComments: 5,
comments: [{
_id: fks9272ewt
user: 49wnf93hr9,
comment: "Hello world"
}, {
_id: j3924je93h
user: 49wnf93hr9,
comment: "Heya"
}, {
_id: 30283jt9dj
user: dje394ifjef,
comment: "Text"
}]
}]
I understand that this could be completed after MongoDB returns the data by splicing, although I think it would be best to do this within the query so that Mongo doesn't have to return all comments for every single post all the time.
Does this solve your problem? try plugging in the _id values and see what you are missing and post them here.
begin with this query
db.collection.aggregate([{$match: {_id: 349348jf49rk}},
{$project:{
_id:1,
user:1,
totalNumberOfComments: { $size: "$comments" },
comments: {$slice:3}
}
}
])

How to find a subdocument by id in mongoose and exclude some fields

I have the a document stored in mongodb:
shop: {
_id: '...'
title: 'my shop'
users: [
{
_id: '...',
name: 'user1',
username: '...'
},
{
_id: '...',
name: 'user2',
username: '...'
}
]
}
I use this query to get a subdocument user by his id:
Shop.findOne({'users._id': userId}, {'users.$': 1}, function (err, user) {
console.log(user);
});
Output:
{ _id: ...,
users:
[{
name: 'user1',
username: '...',
_id: ...
}]
}
How can I filter the result to only return the user name.
The way I do it now:
Shop.findOne({'users._id': userId}, {'users.$': 1}, function (err, shop) {
shop = shop.toObject()
user = shop.users[0]
filtered = {
name: user.name
}
callback(filtered);
});
But is there a better way to do it all in the query?
This question is almost two years old, but I noticed that people are still looking for a solution to this problem. fernandopasik's answer helped me very much, but is missing a code sample on how to use the suggested aggregation operations. That's why I post a more detailed answer.
The document I used is:
{
_id: '...'
title: 'my shop'
users: [
{
_id: 'user1Id',
name: 'user1',
username: '...'
},
{
_id: 'user2Id',
name: 'user2',
username: '...'
}
]
}
The solution I came up with (after reading the mongodb docs about aggregation) was:
Shop.aggregate([
{$unwind: '$users'},
{$match: {'users._id': 2}},
{$project: {_id: 0, 'name': '$users.name'}}
]);
To understand how the aggregation is working, it's best to try one operation at a time and read the mongodb docs of this operation.
Shop.aggregate([{$unwind: '$users'}])
$unwind deconstructs the users array (don't forget to include $ on the array name), so you end up with:
{
_id: '...',
title: 'my shop',
users: {
_id: 'user1Id',
name: 'user1',
username: '...'
}
}
{
_id: '...',
title: 'my shop',
users: {
_id: 'user2Id',
name: 'user2',
username: '...'
}
}
2. Using {$match: {'users._id': 'user2Id'}} on the aggregation pipeline (the two docs in this example) will return the whole document where users._id is 'user2Id':
{
_id: '...',
title: 'my shop',
users: {
_id: 'user2Id',
name: 'user2',
username: '...'
}
}
3. to return only name: 'user2' you can use {$project: {_id: 0, 'name': '$users.name'}}:
{name: 'user2'}
The aggregation pipeline is not easy to grasp at first. I recommend reading through the mongodb aggregation docs and try one aggregation operation at a time. It is sometimes hard to spot the error in the whole aggregation pipeline. Most of the time you simply get no result document from the pipeline when there is an error somewhere in the pipeline.
You should try mongodb aggregation framework with mongoose:
http://mongoosejs.com/docs/api.html#aggregate-js
I suggest you first apply unwind users and then match the user id and then project the username.

Mongodb: limit 2-level nested document

Mongod deb albums collection contains such items:
var album = {
name: 'album1',
tracks: [{
title: 'track0',
language: 'en',
processing: {
tasks: [
{_id: 1, name: 'someTask1'},
{_id: 2, name: 'someTask2'},
]
}
},{
title: 'track1',
language: 'en',
},{
title: 'track2',
language: 'es',
}]
}
I need to select only one Album, track0 and task with _id 1, so that result set would be looking like (contains only one track and only one task)
{
name: 'album1'
tracks: [{
title: 'track0',
language: 'en',
processing: {
tasks: [
{_id: 1, name: 'someTask1', },
]
}
]
}
Is it possible to that without aggregation framework just using find?
I tried $elemMatch and .$ projection to limit output, but it seems that it doesn't work on nested levels > 1 (tasks in that case) =(