How to get only document from sub-document's array in mongoose - node.js

I have a Chapter schema like this:
const ChapterSchema = new Schema({
title: {
type: String,
required: [true, 'Chapter title is required']
},
topics: { type: [TopicSchema] }
})
So, there is a topics array as sub-documents of Chapter.
I want to get a particular topic by its _id from Chapter. For that I've tried this query below:
let data = await Chapter.findOne({ "topics._id": _id })
return res.json(data)
But it returns a whole chapter of this topic with topic sibling like this:
{
"_id": "5e504271ee36f61ba8d76f37",
"title": "Roshayon Chapter 2",
"topics": [
{
"_id": "5e52bdf994b60b4c540cab33",
"title": "topic 4",
"intro": "<p><b>This text is bold</b></p><p><i>This text is italic</i></p><p>This is<sub> subscript</sub> and <sup>superscript</sup></p>"
},
{
"_id": "5e52bdf994b60b4c540cab34",
"title": "topic 5",
"intro": "<p><b>This text is bold</b></p><p><i>This text is italic</i></p><p>This is<sub> subscript</sub> and <sup>superscript</sup></p>"
}
]
}
I don't need whole chapter as above. I just need a single topic object which I am looking for by its id.
How can I able to get
Expected result:
{
"_id": "5e52bdf994b60b4c540cab34",
"title": "topic 5",
"intro": "<p><b>This text is bold</b></p><p><i>This text is italic</i></p><p>This is<sub> subscript</sub> and <sup>superscript</sup></p>"
}

You need to use $elemMatch while projecting so it gives us the matching array rec ord.
something like this should work
let data = await Chapter.findOne({ "topics._id": _id }, {_id: 0, topics: {$elemMatch: {_id: _id}}});
If you need to get the just object, you can use aggregate and use following query
await Chapter.aggregate([
{$match: {'topics._id': "5e52bdf994b60b4c540cab33"}},
{$project: {
topics: {$filter: {
input: '$topics',
as: 'topic',
cond: {$eq: ['$$topic._id', '5e52bdf994b60b4c540cab33']}
}},
_id: 0
}}
]).unwind("topics").exec()
Hope it helps.

Related

How can I get only the array element as output instead of whole object in MongoDB?

Below is my code to display review array data which is part of the restaurant collection object:
async get(reviewId) {
const restaurantsCollection = await restaurants();
reviewId = ObjectId(reviewId)
const r = await restaurantsCollection.findOne(
{ reviews: { $elemMatch: { _id : reviewId } } },
{"projection" : { "reviews.$": true }}
)
return r
}
My object looks like:
{
_id: '6176e58679a981181d94dfaf',
name: 'The Blue Hotel',
location: 'Noon city, New York',
phoneNumber: '122-536-7890',
website: 'http://www.bluehotel.com',
priceRange: '$$$',
cuisines: [ 'Mexican', 'Italian' ],
overallRating: 0,
serviceOptions: { dineIn: true, takeOut: true, delivery: true },
reviews: []
}
My output looks like:
{
"_id": "6174cfb953edbe9dc5054f99", // restaurant Id
"reviews": [
{
"_id": "6176df77d4639898b0c155f0", // review Id
"title": "This place was great!",
"reviewer": "scaredycat",
"rating": 5,
"dateOfReview": "10/13/2021",
"review": "This place was great! the staff is top notch and the food was delicious! They really know how to treat their customers"
}
]
}
What I want as output:
{
"_id": "6176df77d4639898b0c155f0",
"title": "This place was great!",
"reviewer": "scaredycat",
"rating": 5,
"dateOfReview": "10/13/2021",
"review": "This place was great! the staff is top notch and the food was delicious! They really know how to treat their customers"
}
How can I get the output as only the review without getting the restaurant ID or the whole object?
So the query operators, find and findOne do not allow "advanced" restructure of data.
So you have 2 alternatives:
The more common approach will be to do this in code, usually people either use some thing mongoose post trigger or have some kind of "shared" function that handles all of these transformations, this is how you avoid code duplication.
Use the aggregation framework, like so:
const r = await restaurantsCollection.aggregate([
{
$match: { reviews: { $elemMatch: { _id : reviewId } } },
},
{
$replaceRoot: {
newRoot: {
$arrayElemAt: [
{
$filter: {
input: "$reviews",
as: "review",
cond: {$eq: ["$$review._id", reviewId]}
}
},
0
]
}
}
}
])
return r[0]

mongoose find object into array of object

I'm new in mongoose and I'm trying to find user by code [user.test.test1.code] , any idea ?
Model :
const userSechema = new mongoose.Schema({
name: {
type: String,
required: true
},
test: [{}],
})
Data :
{
"_id": {
"$oid": "600020ab34742c2d34ae45e5"
},
"test": [{
"test1": {
"code": 11111
},
"test2": {
"code": 22222
}
}]
"name": "daniel"
}
query :
let regex = new RegExp(req.query.searchUserKey, 'i')
const users = await User.find({ $or: [{'name': regex },{'test.test1': { code : regex} }]})
-- Solution --
Thanks you guys, both answers is work for me
Is as simple as do "test.test1.code": 418816 into find query like this:
db.collection.find({
"test.test1.code": 418816
})
This query will give you all documents where exists test.test1.code with value 418816.
Note that this query return the whole document, not only the sub-document into the array. But I'm assuming by your post that a user is the document where exists the field name.
Example here
you can use $elemMatch, check the documentation
const users = await User.find(
{ test: { $elemMatch: { "test1.code": 418816 } } }
)

MongoDB - findOne with nested subdocuments and projection

I am currently using the code below in node.js to find and return data on various nesting levels from a mongo database. I'd like to add another layer of nesting (as mentioned in #3).
Collection:
[
{
"title": "Category A",
"link": "a",
"items": [
{
"title": "Item C",
"link": "a-c",
"series": [
{
"title": "Item C X",
"link": "a-c-x"
},
{
"title": "Item C Y",
"link": "a-c-y"
},
]
},
{
"title": "Item D",
"link": "a-d"
}
]
},
{
"title": "Category B",
"link": "b"
}
]
The query:
const doc = await ... .findOne(
{
$or: [
{ link: id },
{ "items.link": id },
{ "items.series.link": id }
],
},
{
projection: {
_id: 0,
title: 1,
link: 1,
items: { $elemMatch: { link: id } },
},
}
);
Intended results:
(works) if link of the document is matched,
(works) there should only be an object with the title and link returned
e.g.
value of id variable: "a"
expected query result: { title: "Category A", link: "a"}
(works) if items.link of subdocument is matched,
(works) it should be the same as above + an additional element in the items array returned.
e.g.
value of id variable: "a-c"
expected query result: { title: "Category A", link: "a", items: [{ title: "Item C", link: "a-c" }]}
(works) if items.series.link of sub-subdocument is matched
(struggling with this) it should return the same as in 2. + an additional element inside the matched items.series
e.g.
value of id variable: "a-c-y"
expected query result: { title: "Category A", link: "a", items: [{ title: "Item C", link: "a-c", series: [{ title: "Item C Y", link: "a-c-y" }]}]}
current query result: The whole Category A document with all sub-documents
Questions:
a.) How do I modify the projection to return the expected output in #3 as well?
b.) Is the approach above sound in terms of reading speed from a denormalized structure? I figured there'd probably need to be indexes on link, items.link and items.series.link as they are all completely unique in the document, but maybe there is a way to achieve the above goal with a completely different approach?
Ended up with going half-way via mongodb and get the full item for both - when the item link is matched and the series link is matched:
projection: {
_id: 0,
title: 1,
link: 1,
items: { $elemMatch: { $or: [
{ link: id },
{"series.link": id }
]}},
}
After that javascript filters the series array to see if the series is matched:
doc?.items?.[0]?.series?.find(item => item.link === id)
if the js is truthy (returns an object) we matched a series, if there is a doc, but the js is falsy we matched an item result.
Although not a full mongodb solution and there is definitely room for improvement the above seems to achieve the end goal to be able to distinguish between category, item and series results.

Mongoose group documents by value but only return latest document each

I have a collection called "Words", where I save different Words in different languages. Words are stored in "content" and their language code in "lang" (en, de, fr).
This is my Words schema:
content: {
type: String
},
lang: {
type: String,
enum: ['en', 'de', 'fr']
}
I am now trying to retrieve the latest stored value for each language, only returning one document each.
This is my desired example output:
[{
lang: "en",
content: "Whats up"
},{
lang: "de",
content: "Guten Tag"
},{
lang: "fr",
content: "Salut"
}]
I've already tried to use aggregate function with group. But now the two letter language code gets returned as the document id:
Words.aggregate([{
$group: {
_id: '$lang'
}
}]
Words.aggregate([{$sort: {'_id': -1}},
{$group: {_id:'$lang',
word: {
$push: {
_id: '$_id',
content: '$content'
}
}
}},
{$project: {
_id:0,
lang:'$_id',
content: {$arrayElemAt:['$word.content',0]}
}}])
First, I used sort on _id by descending order. (Assuming you had use mongoDB auto-generated _id)
Next, group all the content by language and lastly project the first content which is the latest according to _id.

mongoose findOne query with multiple conditions and "$gt" not returning proper results

I am stumped by mongoose. I am trying to find a record that matches on both instanceId and currentQuantity. I can see the records in the database, and I know that currentQuantity is numeric not character. But these records are not being found by my query.
I have tried this:
titleRecords.findOne({"titleByDate.instanceId": 100156
})
Result: returns the queries, but doesn't include the second condition which I need. So I tried this:
titleRecords.findOne({"titleByDate.instanceId": 100156
, "titleByDate.currentQuantity": {"$gt": 0}
})
Result: doesn't return the records I'm looking for (returns no records at all)
And I have tried this with extra brackets:
titleRecords.findOne({{"titleByDate.instanceId": 100156
, "titleByDate.currentQuantity": {"$gt": 0}
}})
Result: I get an error that the first { is unexpected
And I have tried this:
titleRecords.findOne({"titleByDate.instanceId": 100156
, "titleByDate.currentQuantity": {$gt: 0}
})
Result: doesn't return any records. I think quotes are required around "$gt" but honestly I'm so confused by mongoose syntax and can't for the life of me find any documentation of how to do this on mongoosejs.com
Per request, here are three sample records. The first query above returns the middle record - none of the others return anything. I apologize if this is not the right format to share the data in - please let me know if there is a better way
{
"_id": {
"$oid": "59936"
},
"titleByDate": {
"currentQuantity": 4,
"title": "ABC",
"instanceId": 150351
},
"__v": 0
},
{
"_id": {
"$oid": "54936"
},
"titleByDate": {
"currentQuantity": 2,
"title": "QPR",
"instanceId": 100156
},
"__v": 0
},
{
"_id": {
"$oid": "51936"
},
"titleByDate": {
"currentQuantity": 0,
"title": "QQQ",
"instanceId": 143159
},
"__v": 0
}
And my model statement:
var mongoose = require('mongoose');
mongoose.Promise = global.Promise;
// define the schema for a play
var titleListingSchema = mongoose.Schema({
title : {
titleId : Number,
title : String,
},
titleByDate :{
instanceId : Number,
title :String,
currentQuantity: Number
}
});
module.exports = mongoose.model('titleListing', titleListingSchema);

Resources