Multiple group in aggregate - mongodb

In my db there are two types of sessions - "studio", "classroom". I want the count of number of studio sessions and number of classroom sessions AND count of subjects in a classroom session and count of subjects in studio session.
Eg data:
[
{
"key": 1,
"type": "studio",
"sub": "english"
},
{
"key": 2,
"type": "studio",
"sub": "history"
},
{
"key": 3,
"type": "classroom",
"sub": "english"
},
{
"key": 4,
"type": "studio",
"sub": "english"
},
{
"key": 5,
"type": "classroom",
"sub": "english"
},
{
"key": 5,
"type": "classroom",
"sub": "geography"
}
]
This is the query I have to group by session type.
db.collection.aggregate([
{
$group: {
_id: "$type",
groupedData: {
$push: {
key: "$key",
"sub": "$sub",
"type": "$type"
}
}
}
}
])
By this I get
[
{
"_id": "classroom",
"groupedData": [
{
"key": 3,
"sub": "english",
"type": "classroom"
},
{
"key": 5,
"sub": "english",
"type": "classroom"
},
{
"key": 5,
"sub": "geography",
"type": "classroom"
}
]
},
{
"_id": "studio",
"groupedData": [
{
"key": 1,
"sub": "english",
"type": "studio"
},
{
"key": 2,
"sub": "history",
"type": "studio"
},
{
"key": 4,
"sub": "english",
"type": "studio"
}
]
}
]
But I want the count of a subject in studio and class room and total count of sessions in studio and classroom
Eg.
{
studio: { count: 3, englishInStudio: 2, historyInStudio: 1 },
classroom: {count: 3, englishInClassroom: 2, geographyInClassroom: 1}
}

You can do this by specifying a document as your key to group on
db.test.aggregate([
{ $group: {
_id: { "type": "$type", "sub" : "$sub"},
count: { $sum: 1}
}
}]);
This will output the following, which get your required values but it's in a different format:
{ "_id" : { "type" : "classroom", "sub" : "english" }, "count" : 2 }
{ "_id" : { "type" : "studio", "sub" : "history" }, "count" : 1 }
{ "_id" : { "type" : "classroom", "sub" : "geography" }, "count" : 1 }
{ "_id" : { "type" : "studio", "sub" : "english" }, "count" : 2 }

Related

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
}

mongodb: How to build facets from an array of objects

What would be the best way to create facets from an array of objects with different attributes for each product.
Example Data:
[
{
"key": 1,
"title": "Product A",
"attrs": [
{
"keyasd": "size",
"value": "small"
},
{
"kedasy": "color",
"value": "red"
}
]
},
{
"key": 2,
"title": "Product B",
"attrs": [
{
"key": "size",
"value": "large"
},
{
"key": "color",
"value": "blue"
}
]
},
{
"key": 3,
"title": "Product C",
"attrs": [
{
"key": "resolution",
"value": "8K"
},
{
"key": "refresh rate",
"value": "60 Hz"
},
]
}
]
The result I would like to get would be something like this:
[
{
"_id": {
"key": "size",
"values" : [
{"title": "small", "count": 1},
{"title": "large", "count": 1}
]
}
},
{
"_id": {
"key": "color",
"values" : [
{"title": "red", "count": 1},
{"title": "blue", "count": 1}
]
}
},
{
"_id": {
"key": "resolution",
"values" : [
{"title": "8K", "count": 1}
]
}
},
{
"_id": {
"key": "refresh rate",
"values" : [
{"title": "60 Hz", "count": 1}
]
}
}
]
I don't know if the result I put is possible, but I need to somehow build it, even if it's individually each facet for each type of attribute that a product can have
db.collection.aggregate([
{
"$unwind": "$attrs"
},
{
"$group": {
"_id": {
k: "$attrs.key",
v: "$attrs.value"
},
"count": { "$sum": 1 }
}
},
{
"$group": {
"_id": "$_id.k",
"values": {
"$push": {
"title": "$_id.v",
"count": "$count"
}
}
}
},
{
"$project": {
"_id": {
key: "$_id",
values: "$values"
}
}
}
])
mongoplayground

Filter nested array with conditions based on multi-level object values and update them - MongoDB aggregate + update

Considering I have the following documents in a collection (ignoring the _id) :
[
{
"Id": "OP01",
"Sessions": [
{
"Id": "Session01",
"Conversations": [
{
"Id": "Conversation01",
"Messages": [
{
"Id": "Message01",
"Status": "read",
"Direction": "inbound"
},
{
"Id": "Message02",
"Status": "delivered",
"Direction": "internal"
},
{
"Id": "Message03",
"Status": "delivered",
"Direction": "inbound"
},
{
"Id": "Message04",
"Status": "sent",
"Direction": "outbound"
}
]
},
{
"Id": "Conversation02",
"Messages": [
{
"Id": "Message05",
"Status": "sent",
"Direction": "outbound"
}
]
}
]
},
{
"Id": "Session02",
"Conversations": [
{
"Id": "Conversation03",
"Messages": [
{
"Id": "Message06",
"Status": "read",
"Direction": "inbound"
},
{
"Id": "Message07",
"Status": "delivered",
"Direction": "internal"
}
]
},
{
"Id": "Conversation04",
"Messages": []
}
]
}
]
},
{
"Id": "OP02",
"Sessions": [
{
"Id": "Session03",
"Conversations": []
}
]
},
{
"Id": "OP03",
"Sessions": []
}
]
First query — aggregate (+$project)
I want to get the list of Messages grouped by their Conversations where:
Sessions.Id: "Session01"
and
Sessions.Conversations.Messages.Direction $in ["inbound", "outbound"]
and
Sessions.Conversations.Messages.Status $in ["sent", "delivered"]
The expected result is:
[
{
"Id": "Conversation01",
"Messages": [
{
"Id": "Message03",
"Status": "delivered",
"Direction": "inbound"
},
{
"Id": "Message04",
"Status": "sent",
"Direction": "outbound"
}
]
},
{
"Id": "Conversation02",
"Messages": [
{
"Id": "Message05",
"Status": "sent",
"Direction": "outbound"
}
]
}
]
A side note:
If on different documents (or on different Sessions) the Sessions.Id: "Session01" condition is verified ("Session01"is not an unique key), the document's Messages that match the other conditions should also be added.
The result output doesn't mention neither the document or Sessions levels.
Second query — update
I want to update the Sessions.Conversations.Messages.Status of all those messages (same condition as before) to "read".
The collection should have now the following documents:
Please note the changes on:
Sessions.Conversations.Messages.Id = "Message03"
Sessions.Conversations.Messages.Id = "Message04"
Sessions.Conversations.Messages.Id = "Message05"
at Sessions.Id = "Session01"
[
{
"Id": "OP01",
"Sessions": [
{
"Id": "Session01",
"Conversations": [
{
"Id": "Conversation01",
"Messages": [
{
"Id": "Message01",
"Status": "read",
"Direction": "inbound"
},
{
"Id": "Message02",
"Status": "delivered",
"Direction": "internal"
},
{
"Id": "Message03",
"Status": "read",
"Direction": "inbound"
},
{
"Id": "Message04",
"Status": "read",
"Direction": "outbound"
}
]
},
{
"Id": "Conversation02",
"Messages": [
{
"Id": "Message05",
"Status": "read",
"Direction": "outbound"
}
]
}
]
},
{
"Id": "Session02",
"Conversations": [
{
"Id": "Conversation03",
"Messages": [
{
"Id": "Message06",
"Status": "read",
"Direction": "inbound"
},
{
"Id": "Message07",
"Status": "delivered",
"Direction": "internal"
}
]
},
{
"Id": "Conversation04",
"Messages": []
}
]
}
]
},
{
"Id": "OP02",
"Sessions": [
{
"Id": "Session03",
"Conversations": []
}
]
},
{
"Id": "OP03",
"Sessions": []
}
]
How can I accomplish these results with an aggregate and update_one queries?
Here comes a visual explanation of both queries:
I have written the aggregation query
db.session.aggregate([
{
$unwind:"$Sessions"
},
{
$unwind:"$Sessions.Conversations"
},
{
$unwind:"$Sessions.Conversations.Messages"
},
{
$match:{
"Sessions.Id" : "Session01",
"Sessions.Conversations.Messages.Direction":{
$in:[
"inbound", "outbound"
]
},
"Sessions.Conversations.Messages.Status":{
$in:[
"sent", "delivered"
]
}
}
},
{
$group:{
"_id":"$Sessions.Conversations.Id",
"Messages":{
$push:"$Sessions.Conversations.Messages"
}
}
}
]).pretty()
Output
{
"_id" : "Conversation02",
"Messages" : [
{
"Id" : "Message05",
"Status" : "sent",
"Direction" : "outbound"
}
]
}
{
"_id" : "Conversation01",
"Messages" : [
{
"Id" : "Message03",
"Status" : "delivered",
"Direction" : "inbound"
},
{
"Id" : "Message04",
"Status" : "sent",
"Direction" : "outbound"
}
]
}
Now to Update the document:
I have used the positional-filters
db.session.update(
{},
{
$set:{
"Sessions.$[session].Conversations.$[].Messages.$[message].Status":"read"
}
},
{
"arrayFilters": [{"session.Id":"Session01"},{ "message.Id": "Message05" }]
}
)
This will update the status as read for "session.Id":"Session01" and "message.Id": "Message05"
Hope this will help you. :)
UPDATE
db.session.update(
{},
{
$set:{
"Sessions.$[session].Conversations.$[].Messages.$[message].Status":"read"
}
},
{
"arrayFilters": [
{
"session.Id":"Session01"
},
{
"message.Direction": {
$in :[
"inbound",
"outbound"
]
},
"message.Status": {
$in :[
"sent",
"delivered"
]
}
}
]
}
)

MongoDB, grouping inner Hash key value by another key value

I have these 4 elements in my collection:
/* 1 */
{
"demographics": [
{
"key": "country",
"value": "ES"
},
{
"key": "city",
"value": "Sevilla"
},
{
"key": "region",
"value": "Andalucía"
}
]
}
/* 2 */
{
"demographics": [
{
"key": "city",
"value": "Cádiz"
},
{
"key": "country",
"value": "ES"
},
{
"key": "region",
"value": "Andalucía"
}
]
}
/* 3 */
{
"demographics": [
{
"key": "country",
"value": "GB"
},
{
"key": "region",
"value": "Greater London"
},
{
"key": "city",
"value": "London"
}
]
}
/* 4 */
{
"demographics": [
{
"key": "country",
"value": "ES"
},
{
"key": "region",
"value": "Andalucía"
},
{
"key": "city",
"value": "Sevilla"
}
]
}
I would like to group them by:
demographic.value when demographic.key = "country"
demographic.value when demographic.key = "region"
demographic.value when demographic.key = "city"
Having a result like this:
{ "values": ["ES", "Andalucía", "Sevilla"], "count": 2 }
{ "values": ["ES", "Andalucía", "Cádiz"], "count": 1 }
{ "values": ["GB", "Greater London", "London"], "count": 1 }
Attention: beware the order of the demographics array elements might be not always the same.
I have tried
db.getCollection('test').aggregate(
[
{ "$unwind": "$demographics" },
{
"$project" :{
"_id": 0,
"demographics.key": 1,
"demographics.value": 1
}
},
{
"$group" : {
"_id": {
"key": "$demographics.key",
"value": "$demographics.value"
},
"count": { "$sum": 1 }
}
},
{
"$group" : {
"_id": "$_id.key",
"values": { "$push": { "value": "$_id.value", "count": "$count" } }
}
}
]
)
This gives me this result:
/* 1 */
{
"_id": "country",
"values": [
{
"value": "GB",
"count": 1.0
},
{
"value": "ES",
"count": 3.0
}
]
}
/* 2 */
{
"_id": "region",
"values": [
{
"value": "Greater London",
"count": 1.0
},
{
"value": "Andalucía",
"count": 3.0
}
]
}
/* 3 */
{
"_id": "city",
"values": [
{
"value": "London",
"count": 1.0
},
{
"value": "Cádiz",
"count": 1.0
},
{
"value": "Sevilla",
"count": 2.0
}
]
}
But this is not the groups I am looking for
You can try running the following pipeline:
db.test.aggregate([
{ "$unwind": "$demographics" },
{ "$sort": { "demographics.key": 1, "demographics.value": 1 } },
{
"$group": {
"_id": "$_id",
"values": { "$push": "$demographics.value" }
}
},
{
"$group": {
"_id": "$values",
"count": { "$sum": 1 }
}
},
{
"$project": {
"_id": 0, "values": "$_id", "count": 1
}
}
])
Sample Output
/* 1 */
{
"count" : 2,
"values" : [
"Sevilla",
"ES",
"Andalucía"
]
}
/* 2 */
{
"count" : 1,
"values" : [
"London",
"GB",
"Greater London"
]
}
/* 3 */
{
"count" : 1,
"values" : [
"Cádiz",
"ES",
"Andalucía"
]
}

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