MongoDb nested object selection [duplicate] - node.js

This question already has answers here:
Return only matched sub-document elements within a nested array
(3 answers)
Closed 5 years ago.
I have the multiple nested objects and lists, like below
{
"_id": "5a76be26ca96e22f08af2a19",
"testId": "123",
"testName": "summerTest",
"subjects": [
{
"subjectName": "Maths",
"testDetails": [
{
"testNumber": "0001",
"startTime": "2/18/18 13:30",
"endTime": "2/18/18 13:30",
"testDuriation": "01:00:00",
"questions": [
{...}
]
},
{
"testNumber": "0002",
"startTime": "2/18/18 13:30",
"endTime": "2/18/18 13:30",
"testDuriation": "01:00:00",
"questions": [
{...}
]
}
]
}
i want to select testNumber 0002 only. using mongoclient in my express js.
collection.find({ "testId": "123", "subjects.subjectName": "Maths", "subjects.testDetails.testNumber": "0002" }).toArray(function (err, data) {}..
But it will return entire TestId 123 document anyone help me. Thanks

Will be available
db.collection.aggregate([
{$unwind : '$subjects'},
{$project : {'_id': 0 , 'array' : '$subjects.testDetails'}},
{$unwind : '$array'},
{$match: {'array.testNumber' : '0002' }}
])

With a find you always return a whole document, so you need to add a projection to only show what you need.
By the way in your find filter there is an error, because if you want to filter only collections with a particular subjects.subjectName and subjects.testDetails.testNumber you need to use $elemMatch (https://docs.mongodb.com/manual/reference/operator/query/elemMatch/). If you don't do this it will return all document where in the subjects array there is one element with the first property and another one with the second property.

Related

how can i sort data with a array element in mongodb without using unwind

this is my sample data in this I have a userId and a array "watchHistory", "watchHistory" array contains the list of videos that is watched by the user :
{
"_id": "62821344445c30b35b441f11",
"userId": 579,
"__v": 0,
"watchHistory": [
{
"seenTime": "2022-05-23T08:29:19.781Z",
"videoId": 789456,
"uploadTime": "2022-03-29T12:33:35.312Z",
"description": "Biography of Indira Gandhi",
"speaker": "andrews",
"title": "Indira Gandhi",
"_id": "628b45df775e3973f3a670ec"
},
{
"seenTime": "2022-05-23T08:29:39.867Z",
"videoId": 789455,
"uploadTime": "2022-03-31T07:37:39.712Z",
"description": "What are some healthy food habits to stay healthy",
"speaker": "morris",
"title": "Healthy Food Habits",
"_id": "628b45f3775e3973f3a670"
},
]
}
I need to match the userId and after that i need to sort it with "watchHistory.seenTime", seenTime field indicates when the user saw the video. so i need to sort like the last watched video should come first in the list.
I don't have permission to use unwind so can any one help me from this. Thank you.
If you are using MongoDB version 5.2 and above, you can use $sortArray operator in an aggregation pipeline. Your pipeline should look something like this:
db.collection.aggregate(
[
{"$match":
{ _id: '62821344445c30b35b441f11' }
},
{
"$project": {
_id: 1,
"userId": 1,
"__v": 1,
"watchHistory": {
"$sortArray": { input: "$watchHistory", sortBy: { seenTime: -1 }}
}
}
}
]
);
Please modify the filter for "$match" stage, according to the key and value you need to filter on. Here's the link to the documentation.
Without using unwind, it's not possible to do it via an aggregation pipeline, but you can use update method and $push operator, as a workaround like this:
db.collection.update({
_id: "62821344445c30b35b441f11"
},
{
$push: {
watchHistory: {
"$each": [],
"$sort": {
seenTime: -1
},
}
}
})
Please see the working example here

How to project specific fields from a queried document inside an array?

here is the document
formId: 123,
title:"XYZ"
eventDate:"2022-04-15T05:40:57.182Z"
responses:[
{
orderId:98422,
name:"XYZ1",
email:"a#gmal.com",
paymentStatus:"pending",
amount:250,
phone:123456789
},
{
orderId:98422,
name:"XYZ1",
email:"a#gmal.com",
paymentStatus:"success",
amount:250,
phone:123456791
}
]
I used $elemMatch to filter the array such that I get only the matched object.
const response = await Form.findOne({ formId:123 }, {
_id:0,
title: 1,
eventDate: 1,
responses: {
$elemMatch: { orderId: 98422 },
},
})
But this returns all the fields inside the object present in the array "responses".
title:"XYZ"
eventDate:"2022-04-15T05:40:57.182Z"
responses:[
{
orderId:98422,
name:"XYZ1",
email:"a#gmal.com",
paymentStatus:"pending",
amount:250,
phone:123456789
}
]
But I want only specific fields to be returned inside the object like this
title:"XYZ"
eventDate:"2022-04-15T05:40:57.182Z"
responses:[
{
name:"XYZ1",
email:"a#gmal.com",
paymentStatus:"pending",
}
]
How can i do that ?
Query
aggregation way to keep some members and edit them also
map on responses if orderId matches keep the fields you want, the others are replaced with null
filter to remove those nulls (members that didnt match)
here 2 matches if you want to keep only one member of the array you can use
[($first ($filter ...)]
*$elemMatch that you used can be combined with the $ project operator to avoid the aggregation, but with $ operator we get all the matching member (here you want only some fields so i think aggregation is the way)
Playmongo
aggregate(
[{"$match": {"formId": {"$eq": 123}}},
{"$project":
{"_id": 0,
"title": 1,
"eventDate": 1,
"responses":
{"$map":
{"input": "$responses",
"in":
{"$cond":
[{"$eq": ["$$this.orderId", 98422]},
{"name": "$$this.name",
"email": "$$this.email",
"paymentStatus": "$$this.paymentStatus"},
null]}}}}},
{"$set":
{"responses":
{"$filter":
{"input": "$responses", "cond": {"$ne": ["$$this", null]}}}}}])

MongoDB is returning all Results in the find function [duplicate]

This question already has answers here:
Retrieve only the queried element in an object array in MongoDB collection
(18 answers)
Closed 3 years ago.
I'm trying to perform a simple find using MongoDB, I'm new on it, so I don't know what is wrong, but it seems that it's always bringing all the results without filtering it.
You can run the code here and see what is happening:
https://mongoplayground.net/
Collection - Paste it here https://mongoplayground.net/ - FIRST Text Area
[
{
"collection": "collection",
"count": 10,
"content": [
{
"_id": "apples",
"qty": 5
},
{
"_id": "bananas",
"qty": 7
},
{
"_id": "oranges",
"qty": {
"in stock": 8,
"ordered": 12
}
},
{
"_id": "avocados",
"qty": "fourteen"
}
]
}
]
Find - Paste it here https://mongoplayground.net/ - SECOND Text Area
db.collection.find({
"content.qty": 5
})
Check the results and you will see the entire JSON as a result.
What Am I doing wrong? Thanks!
You can use $filter with $project after your $match in order to get just one item from the array:
db.collection.aggregate([
{ $match: { "content.qty": 5 }},
{
$project: {
collection: 1,
count: 1,
content: {
$filter: {
input: "$content",
as: "item",
cond: { $eq: [ "$$item.qty", 5 ]
}
}
}
}
}
])
Without having to unwind etc. You are getting all since $find returns the first document which matches and in your case that is the main doc.
See it working here
First, the query is bringing back what it should do. it bring you the document that satisfy your query, try to add element or more in the array you search in to see the difference.
Secondly, what you want to reach - to get only the specific elements in the nested array - can be done by aggregation you can read in it more here:
https://docs.mongodb.com/manual/aggregation/

Search and Push Array to Nested Object Array in MongoDB [duplicate]

This question already has answers here:
Mongodb $push in nested array
(4 answers)
Closed 4 years ago.
"date_added": {
"$date": "2018-02-27T21:34:31.144Z"
},
"malls": [
{
"name": "DFM",
"geocoordinates": "-6.7726935,39.2196418",
"region": "Kentucky",
"show_times": [],
"_id": {
"$oid": "5a95d3ed053cc1444eadaeae"
}
},
{
"name": "MkHouse",
"geocoordinates": "-6.8295944,39.2738459",
"region": "Kenon",
"show_times": [],
"_id": {
"$oid": "5a95d429053cc1444eadaeaf"
}
}
],
"title": "Black Panther",
I need to find/query malls with name == "DFM" and push data to show_times array, can anybody help! Which is the best way to handle this. I already query using _id and it worked and have this document. Now how can i push show_times? I'm using mongoose v5.5.1
Try this, It basically inserts the showtime where it found name field in malls array equal to DFM, $ operator is used for this
model.update(
{ _id: "givenObjectId",
"malls.name" : "DFM"
},
{
$push : {"malls.$.show_times" : data }
}
)
More details on the $ operator

how to query nested array of objects in mongodb?

i am trying to query nested array of objects in mongodb from node js, tried all the solutions but no luck. can anyone please help this on priority?
I have tried following :
{
"name": "Science",
"chapters": [
{
"name": "ScienceChap1",
"tests": [
{
"name": "ScienceChap1Test1",
"id": 1,
"marks": 10,
"duration": 30,
"questions": [
{
"question": "What is the capital city of New Mexico?",
"type": "mcq",
"choice": [
"Guadalajara",
"Albuquerque",
"Santa Fe",
"Taos"
],
"answer": [
"Santa Fe",
"Taos"
]
},
{
"question": "Who is the author of beowulf?",
"type": "notmcq",
"choice": [
"Mark Twain",
"Shakespeare",
"Abraham Lincoln",
"Newton"
],
"answer": [
"Shakespeare"
]
}
]
},
{
"name": "ScienceChap1test2",
"id": 2,
"marks": 20,
"duration": 30,
"questions": [
{
"question": "What is the capital city of New Mexico?",
"type": "mcq",
"choice": [
"Guadalajara",
"Albuquerque",
"Santa Fe",
"Taos"
],
"answer": [
"Santa Fe",
"Taos"
]
},
{
"question": "Who is the author of beowulf?",
"type": "notmcq",
"choice": [
"Mark Twain",
"Shakespeare",
"Abraham Lincoln",
"Newton"
],
"answer": [
"Shakespeare"
]
}
]
}
]
}
]
}
Here is what I've tried so far but still can't get it to work
db.quiz.find({name:"Science"},{"tests":0,chapters:{$elemMatch:{name:"ScienceCh‌​ap1"}}})
db.quiz.find({ chapters: { $elemMatch: {$elemMatch: { name:"ScienceChap1Test1" } } }})
db.quiz.find({name:"Science"},{chapters:{$elemMatch:{$elemMatch:{name:"Scienc‌​eChap1Test1"}}}}) ({ name:"Science"},{ chapters: { $elemMatch: {$elemMatch: { name:"ScienceChap1Test1" } } }})
Aggregation Framework
You can use the aggregation framework to transform and combine documents in a collection to display to the client. You build a pipeline that processes a stream of documents through several building blocks: filtering, projecting, grouping, sorting, etc.
If you want get the mcq type questions from the test named "ScienceChap1Test1", you would do the following:
db.quiz.aggregate(
//Match the documents by query. Search for science course
{"$match":{"name":"Science"}},
//De-normalize the nested array of chapters.
{"$unwind":"$chapters"},
{"$unwind":"$chapters.tests"},
//Match the document with test name Science Chapter
{"$match":{"chapters.tests.name":"ScienceChap1test2"}},
//Unwind nested questions array
{"$unwind":"$chapters.tests.questions"},
//Match questions of type mcq
{"$match":{"chapters.tests.questions.type":"mcq"}}
).pretty()
The result will be:
{
"_id" : ObjectId("5629eb252e95c020d4a0c5a5"),
"name" : "Science",
"chapters" : {
"name" : "ScienceChap1",
"tests" : {
"name" : "ScienceChap1test2",
"id" : 2,
"marks" : 20,
"duration" : 30,
"questions" : {
"question" : "What is the capital city of New Mexico?",
"type" : "mcq",
"choice" : [
"Guadalajara",
"Albuquerque",
"Santa Fe",
"Taos"
],
"answer" : [
"Santa Fe",
"Taos"
]
}
}
}
}
$elemMatch doesn't work for sub documents. You can use the aggregation framework for "array filtering" by using $unwind.
You can delete each line from the bottom of each command in the aggregation pipeline in the above code to observe the pipelines behavior.
You should try the following queries in the mongodb simple javascript shell.
There could be Two Scenarios.
Scenario One
If you simply want to return the documents that contain certain chapter names or test names for example just one argument in find will do.
For the find method the document you want to be returned is specified by the first argument. You could return documents with the name Science by doing this:
db.quiz.find({name:"Science"})
You could specify criteria to match a single embedded document in an array by using $elemMatch. To find a document that has a chapter with the name ScienceChap1. You could do this:
db.quiz.find({"chapters":{"$elemMatch":{"name":"ScienceChap1"}}})
If you wanted your criteria to be a test name then you could use the dot operator like this:
db.quiz.find({"chapters.tests":{"$elemMatch":{"name":"ScienceChap1Test1"}}})
Scenario Two - Specifying Which Keys to Return
If you want to specify which keys to Return you can pass a second argument to find (or findOne) specifying the keys you want. In your case you can search for the document name and then provide which keys to return like so.
db.quiz.find({name:"Science"},{"chapters":1})
//Would return
{
"_id": ObjectId(...),
"chapters": [
"name": "ScienceChap2",
"tests: [..all object content here..]
}
If you only want to return the marks from each test object you can use the dot operator to do so:
db.quiz.find({name:"Science"},{"chapters.tests.marks":1})
//Would return
{
"_id": ObjectId(...),
"chapters": [
"tests: [
{"marks":10},
{"marks":20}
]
}
If you only want to return the questions from each test:
db.quiz.find({name:"Science"},{"chapters.tests.questions":1})
Test these out. I hope these help.

Resources