MongoDB Get concatenated values in a find method [duplicate] - node.js

Let's say I have a collection called 'people' with the following documents:
{
"name": "doug",
"colors": ["blue", "red"]
}
{
"name": "jack",
"colors": ["blue", "purple"]
}
{
"name": "jenny",
"colors": ["pink"]
}
How would I get a concatenated array of all the colors subarrays, i.e.?
["blue", "red", "blue", "purple", "pink"]

Well, Try should work fine for you!!
db.people.distinct("colors")

Try to use aggregate:
db.people.aggregate([
{$unwind:"$colors"},
{$group:{_id:null, clrs: {$push : "$colors"} }},
{$project:{_id:0, colors: "$clrs"}}
])
Result:
{
"result" : [
{
"colors" : [
"blue",
"red",
"blue",
"purple",
"pink"
]
}
],
"ok" : 1
}
Updated
If you want to get unique values in result's array, you could use $addToSet operator instead of $push in the $group stage.

Related

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]}}}}}])

Distinct count of multiple fields values using mongodb aggregation

I'm trying to count distinct values of multiple fields By one MongoDB Aggregation query.
So here's my data:
{
"_id":ObjectID( "617b0dbacda6cbd1a0403f68")
"car_type": "suv",
"color": "red",
"num_doors": 4
},
{
"_id":ObjectID( "617b0dbacda6cbd1a04078df")
"car_type": " suv ",
"color": "blue",
"num_doors": 4
},
{
"_id":ObjectID( "617b0dbacda6cbd1a040ld45")
"car_type": "wagon",
"color": "red",
"num_doors": 4
},
{
"_id":ObjectID( "617b0dbacda6cbd1a0403dcd")
"car_type": "suv",
"color": "blue",
"num_doors": 4
},
{
"_id":ObjectID( "617b0dbacda6cbd1a0403879")
"car_type": " wagon ",
"color": "red",
"num_doors": 4
},
{
"_id":ObjectID( "617b0dbacda6cbd1a0405478")
"car_type": "wagon",
"color": "red",
"num_doors": 4
}
I want a distinct count of each color by car_type:
"car_type": "suv"
"red":2,
"blue":2
iwas able to distinct and cound all colors but i couldnt distinct them by car_type
Query
group specific first (cartype+color), to count the same colors
group less specific after (cartype), to get all colors/count for each car_type
project to fix structure and $arrayToObject to make the colors keys and the the count values
*query assumes that " wagon " was typing mistake(the extra spaces i mean), if your collection has those problems, use $trim to clear the database from those.
*query is updated to include the sum also, from the comment
Test code here
aggregate(
[{"$group":
{"_id": {"car_type": "$car_type", "color": "$color"},
"count": {"$sum": 1}}},
{"$group":
{"_id": "$_id.car_type",
"colors": {"$push": {"k": "$_id.color", "v": "$count"}}}},
{"$set": {"sum": {"$sum": "$colors.v"}}},
{"$project":
{"_id": 0,
"sum": 1,
"car_type": "$_id",
"colors": {"$arrayToObject": ["$colors"]}}},
{"$replaceRoot": {"newRoot": {"$mergeObjects": ["$colors", "$$ROOT"]}}},
{"$project": {"colors": 0}}])

Aggregate a set of unique values from all documents in CouchDB

I'm trying to create a view in CouchDB that returns groups of unique values. E.g., a list of all unique brands and categories.
map function
function (doc) {
emit("brands", [doc.brand]);
emit("categories", [doc.category]);
}
reduce function
function (keys, values, rereduce) {
return values.reduce(function(acc, value) {
if (acc.indexOf(value[0]) === -1) {
return acc.concat(value);
}
return acc;
});
}
then I call that view with group=true, group_level=2. The grouping is correct, but the values aren't unique. The value is an array containing duplicates.
What I'm trying to achieve is basically having the key be the group name, e.g., brands, and the value be the aggregated unique values, e.g., ["Brand A", "Brand B"].
Given the following documents
[
{
"_id": "1",
"brand": "Brand A",
"category": "Category A",
"colors": [
"Red",
"White"
]
},
{
"_id": "2",
"brand": "Brand B",
"category": "Category B",
"colors": [
"Blue",
"White"
]
},
{
"_id": "3",
"brand": "Brand A",
"category": "Category B",
"colors": [
"Green",
"Red"
]
}
]
When I query then view in CouchDB, I'd like to get the following result back
{
"brands": ["Brand A", "Brand B"],
"categories": ["Category A", "Category B"],
"colors": ["Red", "White", "Blue", "Green"]
}
Note: The result above is just a demonstration of what I expect the view to return. It does not have to be structured as such (not even sure it's possible).
I'm going to answer this myself.
First, we want to define a map function that emit the group name as the key and the value wrapped in an array (making rereduce easier).
map function
function (doc) {
emit("brands", [doc.brand]);
emit("categories", [doc.category]);
doc.colors.forEach(function(color) {
emit("colors", [color]);
})
}
The we define a custom reduce function
function (keys, values, rereduce) {
return values.reduce(function(acc, value) {
value.forEach(function(v) {
if (acc.indexOf(v) === -1) {
return acc.push(v);
}
});
return acc;
});
}
Now, calling the view with group=true and group_level=1 will yield the following result:
+------------+-----------------------------------+
| key | value |
+------------+-----------------------------------+
| brands | ["Brand A", "Brand B"] |
| categories | ["Category A", "Category B"] |
| colors | ["Red", "White", "Blue", "Green"] |
+------------+-----------------------------------+

MongoDb nested object selection [duplicate]

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.

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