Mongodb Document : Is there any way to count sub-documents with a condition? - mongodb

I have documents with subdocuments where the field "stars" in the subdocuments have either "1" or "0". I want to count them at the document level based on those values in the field "stars".
This is how the current document looks like:
{
"name": "Hotel A",
"category": "hotel",
"reviews": [
{
"title": "A",
"stars": 1
},
{
"title": "B",
"stars": 1
},
{
"title": "C",
"stars": 0
}
],
"total_reviews": 3
},{
"name": "Hotel B",
"category": "hotel",
"reviews": [
{
"title": "A",
"stars": 1
},
{
"title": "B",
"stars": 1
},
{
"title": "C",
"stars": 0
},
{
"title": "D",
"stars": 0
},
{
"title": "E",
"stars": 1
},
{
"title": "F",
"stars": 0
}
],
"total_reviews": 6
}
And this is the expected output:
{
"name": "Hotel A",
"category": "hotel",
"reviews": [
{
"title": "A",
"stars": 1
},
{
"title": "B",
"stars": 1
},
{
"title": "C",
"stars": 0
}
],
"positive_reviews": 2,
"negative_reviews": 1,
"total_reviews": 3
},{
"name": "Hotel B",
"category": "hotel",
"reviews": [
{
"title": "A",
"stars": 1
},
{
"title": "B",
"stars": 1
},
{
"title": "C",
"stars": 0
},
{
"title": "D",
"stars": 0
},
{
"title": "E",
"stars": 1
},
{
"title": "F",
"stars": 0
}
],
"positive_reviews": 3,
"negative_reviews": 3,
"total_reviews": 6
}
By adding two new fields: "positive_reviews" if {"reviews.stars":1} and "negative_reviews" if {"reviews.stars":0} with the count values

Try as below:
db.collection.aggregate([
{
$project: {
"_id" : 1,
"name" : 1,
"category" : 1,
"reviews" : 1,
"positive_reviews":
{
$reduce:
{
input: "$reviews",
initialValue: 0,
in: {
$add: ["$$value", { $cond:[{ $eq: ["$$this.stars", 1]} , 1, 0 ] } ]
}
}
},
"negative_reviews":
{
$reduce:
{
input: "$reviews",
initialValue: 0,
in: {
$add: ["$$value", { $cond:[{ $eq: ["$$this.stars", 0]} , 1, 0 ] } ]
}
}
},
"total_reviews": { $size: "$reviews"}
}
}
])
Result Response:
/* 1 createdAt:12/04/2019, 18:24:50*/
{
"_id" : ObjectId("5cb08a9a952e3a179190d996"),
"name" : "Hotel A",
"category" : "hotel",
"reviews" : [
{
"title" : "A",
"stars" : 1
},
{
"title" : "B",
"stars" : 1
},
{
"title" : "C",
"stars" : 0
}
],
"positive_reviews" : 2,
"negative_reviews" : 1,
"total_reviews" : NumberInt(3)
},
/* 2 createdAt:12/04/2019, 18:24:50*/
{
"_id" : ObjectId("5cb08a9a952e3a179190d997"),
"name" : "Hotel B",
"category" : "hotel",
"reviews" : [
{
"title" : "A",
"stars" : 1
},
{
"title" : "B",
"stars" : 1
},
{
"title" : "C",
"stars" : 0
},
{
"title" : "D",
"stars" : 0
},
{
"title" : "E",
"stars" : 1
},
{
"title" : "F",
"stars" : 0
}
],
"positive_reviews" : 3,
"negative_reviews" : 3,
"total_reviews" : NumberInt(6)
}

Related

Mongo DB Group and Count based on 2 fields(direct field and nested field)

I am new to Mongo DB query. Here I have a collection of documents in the below structure. As a top level, need to group based on label and below that based on role and their corresponding status grouping. Since it is nested I have tried with $objectToArray
[
{
"_id": "637f58f205b3fcaa10767ab6",
"status": {
"MAN": "inprogress",
"DEV": "new",
"TEST": "new"
},
"label": "ABC1"
},
{
"_id": "637f58f205b3fcaa10767ab7",
"status": {
"MAN": "inprogress",
"DEV": "completed",
"TEST": "inprogress"
},
"label": "ABC2"
},
{
"_id": "637f58f205b3fcaa10767ab8",
"status": {
"MAN": "completed",
"DEV": "new",
"TEST": "completed"
},
"label": "ABC3"
},
{
"_id": "637f58f205b3fcaa10767ab9",
"status": {
"MAN": "new",
"DEV": "completed",
"TEST": "inprogress"
},
"label": "ABC1"
},
{
"_id": "637f58f205b3fcaa10767ac1",
"status": {
"MAN": "new",
"DEV": "completed",
"TEST": "inprogress"
},
"label": "ABC2"
}
]
I am using the following aggregate:
db.getCollection("requirements").aggregate([
{ "$addFields": {
"status": {
"$map": {
"input": { "$objectToArray": "$status" },
"as": "el",
"in": {
"$concat": ["$$el.k", "_", "$$el.v"]
}
}
}
} },
{ "$unwind": "$status" },
{
$group: {
_id: {label: "$label",roleGroup: "$status"},
totalCount: { $sum: 1}
}
},
{
$group:{
_id:"$_id.label",
roleGroups:{$push:{roleGroup:"$_id.roleGroup", totalCount:"$totalCount"}}
}
}
])
And am able to get the following output:
{
"_id" : null,
"counts" : [
{
"k" : "ABC3",
"v" : [
{
"roleGroup" : "DEV_completed",
"totalCount" : 1.0
},
{
"roleGroup" : "MAN_inprogress",
"totalCount" : 1.0
},
{
"roleGroup" : "MAN_completed",
"totalCount" : 1.0
},
{
"roleGroup" : "MAN_new",
"totalCount" : 1.0
},
{
"roleGroup" : "DEV_inprogress",
"totalCount" : 1.0
},
{
"roleGroup" : "DEV_new",
"totalCount" : 1.0
},
{
"roleGroup" : "TEST_new",
"totalCount" : 2.0
},
{
"roleGroup" : "TEST_inprogress",
"totalCount" : 1.0
}
]
},
{
"k" : "ABC2",
"v" : [
{
"roleGroup" : "DEV_inprogress",
"totalCount" : 1.0
},
{
"roleGroup" : "MAN_completed",
"totalCount" : 1.0
},
{
"roleGroup" : "MAN_new",
"totalCount" : 1.0
},
{
"roleGroup" : "TEST_new",
"totalCount" : 2.0
},
{
"roleGroup" : "DEV_new",
"totalCount" : 1.0
}
]
},
{
"k" : "ABC4",
"v" : [
{
"roleGroup" : "TEST_new",
"totalCount" : 1.0
},
{
"roleGroup" : "DEV_new",
"totalCount" : 1.0
},
{
"roleGroup" : "MAN_new",
"totalCount" : 1.0
}
]
},
{
"k" : "ABC1",
"v" : [
{
"roleGroup" : "TEST_new",
"totalCount" : 3.0
},
{
"roleGroup" : "MAN_new",
"totalCount" : 3.0
},
{
"roleGroup" : "DEV_new",
"totalCount" : 3.0
}
]
}
]
}
But I want the output like below:
[
{
"_id": "ABC1",
"MAN": {
"new": 1,
"inprogress": 2,
"completed": 1
},
"DEV": {
"new": 1,
"inprogress": 2,
"completed": 1
},
"TEST": {
"new": 1,
"inprogress": 2,
"completed": 1
},
"totalCount": 4
},
{
"_id": "ABC2",
"MAN": {
"new": 1,
"inprogress": 2,
"completed": 1
},
"DEV": {
"new": 1,
"inprogress": 2,
"completed": 1
},
"TEST": {
"new": 1,
"inprogress": 2,
"completed": 1
},
"totalCount": 4
}
]
I really need some of your inputs to get this format. Thanks in advance

MongoDb : query to match in same embedded document

Help me to match in single embedded document of both conditions.
db.inventory.insertOne([
... { "item": "journal", "instock": [ { "warehouse": "A", "qty": 5 }, { "warehouse": "C", "qty": 15 } ] },
... { "item": "notebook", "instock": [ { "warehouse": "C", "qty": 5 } ] },
... { "item": "paper", "instock": [ { "warehouse": "A", "qty": 60 }, { "warehouse": "B", "qty": 15 } ] },
... { "item": "planner", "instock": [ { "warehouse": "A", "qty": 40 }, { "warehouse": "B", "qty": 5 } ] },
... { "item": "postcard", "instock": [ { "warehouse": "B","qty": 15 }, { "warehouse": "C", "qty": 35 } ] }
... ])
Expected to return single document which match exactly as queried but returns two.
db.inventory.find( { "instock.qty": 5, "instock.warehouse": "A" } )
{ "_id" : ObjectId("63061a1bb87c1278047a2717"), "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] }
{ "_id" : ObjectId("63061a1bb87c1278047a271a"), "item" : "planner", "instock" : [ { "warehouse" : "A", "qty" : 40 }, { "warehouse" : "B", "qty" : 5 } ] }
You need to perform the following query:
db.inventory.find({
instock: { $elemMatch: { qty: 5, warehouse: 'A' } }
});
Documentation for reference: https://www.mongodb.com/docs/v5.0/tutorial/query-array-of-documents/#a-single-nested-document-meets-multiple-query-conditions-on-nested-fields

I am having difficulty in querying the follwing nested document using pymongo

If these are the following nested documents
[
{
"_id": 5,
"name": "Wilburn Spiess",
"scores": [
{
"score": 44.87186330181261,
"type": "exam"
},
{
"score": 25.72395114668016,
"type": "quiz"
},
{
"score": 63.42288310628662,
"type": "homework"
}
]
},
{
"_id": 6,
"name": "Jenette Flanders",
"scores": [
{
"score": 37.32285459166097,
"type": "exam"
},
{
"score": 28.32634976913737,
"type": "quiz"
},
{
"score": 81.57115318686338,
"type": "homework"
}
]
},
{
"_id": 7,
"name": "Salena Olmos",
"scores": [
{
"score": 90.37826509157176,
"type": "exam"
},
{
"score": 42.48780666956811,
"type": "quiz"
},
{
"score": 96.52986171633331,
"type": "homework"
}
]
}
]
I need to access the score part 'type' = exam.
Can somebody help me with this?
If you're asking for a python program to access the score, you can print them out like:
collection = mongo_connection['db']['collection']
documents = collection.find({})
for doc in documents:
for score in doc['scores']:
if score['type'] == 'exam':
print(f'Score: {score["score"]}')
If you are trying to retrieve only the scores and ignore the rest, I'd do an $unwind on the scores, $match on the type, and then project the fields you want (or not).
db.test.aggregate([
{
$unwind: '$scores'
},
{
$match: {
'scores.type': 'exam'
}
},
{
$project: {
'name': '$name',
'score': '$scores.score'
}
}
])
This would output:
{
"_id" : 5,
"name" : "Wilburn Spiess",
"score" : 44.8718633018126
},
{
"_id" : 6,
"name" : "Jenette Flanders",
"score" : 37.322854591661
},
{
"_id" : 7,
"name" : "Salena Olmos",
"score" : 90.3782650915718
}

Mongo DB aggregation to convert books to array of authors

My book collection looks like this:
{
"book": {
"title": "Book1",
"author": "Tom"
},
"quantity": 3
},
{
"book": {
"title": "Book2",
"author": "Tom"
},
"quantity": 4
},
{
"book": {
"title": "Book3",
"author": "Dick"
},
"quantity": 9
},
{
"book": {
"title": "Book4",
"author": "Harry"
},
"quantity": 6
},
{
"book": {
"title": "Book5",
"author": "Chris"
},
"quantity": 7
},
{
"book": {
"title": "Book6",
"author": "Dick"
},
"quantity": 10
}
This collection has book documents with title, author and quantity.
I want help in coming up with aggregation which would output the array of authors with their books and quantity. Example - Tom has authored "Book1" and "Book2", "Dick" has authored "Book6" and "Book3".
authors: [
{
"name": "Tom",
"books": [
{
"title": "Book1",
"quantity": "3"
},
{
"title": "Book2",
"quantity": "4"
}
]
},
{
"name": "Dick",
"books": [
{
"title": "Book6",
"quantity": "10"
},
{
"title": "Book3",
"quantity": "9"
}
]
},
{
"name": "Harry",
"books": [
{
"title": "Book4",
"quantity": "6"
}
]
}
]
Try this aggregation:
db.collection.aggregate([{
$group: {
_id: "$book.author",
books: {
$push: {
title: "$book.title",
quantity: "$quantity"
}
}
}},{
$project: {
"name": "$_id",
"books": 1,
"_id": 0
}}])
I tried it on mongo playground:
https://mongoplayground.net/p/m-QbX-uzkkt
Try as below:
db.collection.aggregate([
{
$group: {
_id: "$book.author",
books: { $push: { "title": "$book.title", "quantity": "$quantity"} }
},
}
])
Output will be
/* 1 */
{
"_id" : "Chris",
"books" : [
{
"title" : "Book5",
"quantity" : 7
}
]
},
/* 2 */
{
"_id" : "Harry",
"books" : [
{
"title" : "Book4",
"quantity" : 6
}
]
},
/* 3 */
{
"_id" : "Dick",
"books" : [
{
"title" : "Book3",
"quantity" : 9
},
{
"title" : "Book6",
"quantity" : 10
}
]
},
/* 4 */
{
"_id" : "Tom",
"books" : [
{
"title" : "Book1",
"quantity" : 3
},
{
"title" : "Book2",
"quantity" : 4
}
]
}

Mongo returning an array element

I have the following JSON document in my mongoDB which I added with mingoimport.
I am trying to return a single element from the questions array where theQuestion equals "q1".
{
"questions": [
{
"questionEntry": {
"id": 1,
"info": {
"seasonNumber": 1,
"episodeNumber": 1,
"episodeName": "Days Gone Bye"
},
"questionItem": {
"theQuestion": "q1",
"attachedElement": {
"type": 1,
"value": ""
}
},
"options": [
{
"type": 1,
"value": "o1"
},
{
"type": 1,
"value": "o1"
}
],
"answer": {
"questionId": 1,
"answer": 1
},
"metaTags": [
"Season 1",
"Episode 1",
"Rick Grimmes"
]
}
},
{
"questionEntry": {
"id": 1,
"info": {
"seasonNumber": 1,
"episodeNumber": 1,
"episodeName": "Days Gone Bye"
},
"questionItem": {
"theQuestion": "q2",
"attachedElement": {
"type": 1,
"value": ""
}
},
"options": [
{
"type": 1,
"value": "o2"
},
{
"type": 1,
"value": "o2"
}
],
"answer": {
"questionId": 1,
"answer": 1
},
"metaTags": [
"Season 1",
"Episode 1",
"Rick Grimmes",
"Glenn Rhee"
]
}
}
]
}
I ran the query db.questions.find({"questions.questionEntry.questionItem.theQuestion" : "q1"}) but this retruned the whole document (both questionEntry's in question array!
I have tried db.questions.find({"questions.questionEntry.questionItem.theQuestion" : "q1"}, _id:0," questions.questionItem": {$elemMatch : {theQuestion: "q1"}}})
But get the following error:
Error: error: {
"$err" : "Can't canonicalize query: BadValue Cannot use $elemMatch projection on a nested field.", "code" : 17287
Is there a way I could limit the result to just the array element which contains it?
Thanks
db.questions.find({},{"questions.questionEntry.questionItem.theQuestion" : "q1"});
or
db.questions.find({"questions.questionEntry.questionItem.theQuestion" : "q1"},{'questions.$':1});
please try these.
If you want to use $elemMatch the query should be:
db.questions.find(
{"questions.questionEntry.questionItem.theQuestion" : "q1"},
{
'_id':0,
"questions": {
$elemMatch : {"questionEntry.questionItem.theQuestion": "q1"}
}
}
)