Mongo DB aggregation to convert books to array of authors - mongodb

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

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
}

Matching a value in subdocument array and matching array to _id

I have a collection of Users which looks like this:
{
"_id": {
"$oid": "5ff6298f2056ad02272d10f2"
},
"name": "Billy Bob",
"story": {
"posts": [
{"id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"},
{"id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"},
{"id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"}
],
"whoCanView": [{
"$oid": "5ff891bd749ed24316272317"
}, {
"$oid": "5ffc89392056ad02272d10f3"
}]
}
}
I want to get all users and their stories listed in an array which I call "contacts". The user currently querying the collection may only get results if their userId is in the "whoCanView" array.
This is what i have so far but doesn't seem to be working:
users.find({'story' : {
'$elementMatch' : {
'_id' : {'$in' : contacts},
ObjectID(userId) : {'$in' : 'story.whoCanView'}
}
}
});
Any guidance would be appreciated. Thanks!
ObjectID(userId) : {'$in' : 'story.whoCanView'}
Above line along with the usage of $elemMatch operator is wrong in your code. Try this query instead:
let contacts = [
ObjectId("5ff6298f2056ad02272d10f2"),
ObjectId("5ff6298f2056ad02272d10f3"),
ObjectId("5ff6298f2056ad02272d10f4")
];
let userId = ObjectId("5ff891bd749ed24316272317");
db.users.find(
{
"_id": { $in: contacts },
"story.whoCanView": userId
},
{
"strories": "$story.posts"
}
);
Output when userId is ObjectId("5ff891bd749ed24316272317"):
/* 1 createdAt:1/7/2021, 2:50:15 AM*/
{
"_id" : ObjectId("5ff6298f2056ad02272d10f2"),
"strories" : [
{
"id" : "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"
},
{
"id" : "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"
},
{
"id" : "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"
}
]
},
/* 2 createdAt:1/7/2021, 2:50:15 AM*/
{
"_id" : ObjectId("5ff6298f2056ad02272d10f3"),
"strories" : [
{
"id" : "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"
},
{
"id" : "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"
},
{
"id" : "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"
}
]
},
/* 3 createdAt:1/7/2021, 2:50:15 AM*/
{
"_id" : ObjectId("5ff6298f2056ad02272d10f4"),
"strories" : [
{
"id" : "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"
},
{
"id" : "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"
},
{
"id" : "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45"
}
]
}
Output when userId is ObjectId("5ff891bd749ed24316272318"):
[]
Test data in users collection:
[
{
"_id": ObjectId("5ff6298f2056ad02272d10f2"),
"name": "Billy Bob",
"story": {
"posts": [
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" },
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" },
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" }
],
"whoCanView": [
ObjectId("5ff891bd749ed24316272317"),
ObjectId("5ffc89392056ad02272d10f3")
]
}
},
{
"_id": ObjectId("5ff6298f2056ad02272d10f3"),
"name": "Billy Bob",
"story": {
"posts": [
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" },
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" },
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" }
],
"whoCanView": [
ObjectId("5ff891bd749ed24316272317"),
ObjectId("5ffc89392056ad02272d10f3")
]
}
},
{
"_id": ObjectId("5ff6298f2056ad02272d10f4"),
"name": "Billy Bob",
"story": {
"posts": [
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" },
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" },
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" }
],
"whoCanView": [
ObjectId("5ff891bd749ed24316272317"),
ObjectId("5ffc89392056ad02272d10f3")
]
}
},
{
"_id": ObjectId("5ff6298f2056ad02272d10f5"),
"name": "Billy Bob",
"story": {
"posts": [
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" },
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" },
{ "id": "b4a12557-4dfa-47eb-9f27-7bbcad0f4c45" }
],
"whoCanView": [
ObjectId("5ff891bd749ed24316272317"),
ObjectId("5ffc89392056ad02272d10f3")
]
}
}
]

MongoDb group by combined with multiple sums and match

I have the fields, group_id, code, revenue. I want to do an aggregation where the revenue is grouped by the group_id and summed up for each type of code. Also, some of the codes will have a combined revenue. So for example, for codes "abc" and "ghi", there will be a combined revenue with header "agi". How do I do this?
If the input like
[
{
"group_id": "ABC",
"revenue": 100,
"code": "abc"
},
{
"group_id": "ABC",
"revenue": 200,
"code": "abc"
},
{
"group_id": "ABC",
"revenue": 200,
"code": "def"
},
{
"group_id": "ABC1",
"revenue": 200,
"code": "def"
},
{
"group_id": "ABC",
"revenue": 200,
"code": "def"
},
{
"group_id": "ABC1",
"revenue": 200,
"code": "abc"
},
{
"group_id": "ABC",
"revenue": 200,
"code": "ghi"
}
]
then the output will be
[
{
"group_id": "ABC",
"expenses": [
{
"code": "agi",
"revenue": 500
},
{
"code": "def",
"revenue": 400
}
]
},
{
"group_id": "ABC1",
"expenses": [
{
"code": "abc",
"revenue": 200
},
{
"code": "def",
"revenue": 200
}
]
},
]
Then group by group_id and code like this:
let combine = ["abc", "ghi"];
db.items.aggregate([
{
$addFields: {
code: {
$cond: {
if: { $in: ["$code", combine] },
then: "agi",
else: "$code"
}
}
}
},
{
$group: {
_id: {
group_id: "$group_id",
code: "$code"
},
revenue: { $sum: "$revenue" }
}
},
{
$group: {
_id: "$_id.group_id",
expenses: {
$push: {
code: "$_id.code",
revenue: "$revenue"
}
}
}
}
]);
Output
/* 1 */
{
"_id" : "ABC",
"expenses" : [
{
"code" : "def",
"revenue" : 400
},
{
"code" : "agi",
"revenue" : 500
}
]
},
/* 2 */
{
"_id" : "ABC1",
"expenses" : [
{
"code" : "def",
"revenue" : 200
},
{
"code" : "agi",
"revenue" : 200
}
]
}

Mongodb - Aggregation and Sum for each object

I need to aggregate for each "Product" object from the sales database, and sum "Price" and "Quantity" to make a product classification.
I was able to group by "Products" but I can't sum Price and Quantity to the each object.
{$group: {
_id: '$products.items',
totalprice:{$sum: "$products.items.price"},
}}
Below sample of sales database, where I need to return the sum of the "Price" and "Quantity" fields sold for each "Products".
{
"_id": {
"$oid": "5d753707c0cd851e80da914c"
},
"created_on": {
"$date": {
"$numberLong": "1567962886000"
}
},
"custumer": {
"name": "Teste",
"cep": "teste",
"address": "teste",
"district": "test",
"city": "test",
"numb": "50",
"comple": "test",
"state": "test",
"cpf": "test",
"birth": "30/09/1977",
"email": "test#gmail.com",
"phone": {
"$numberDouble": "1111111111111"
},
"gender": "M",
"portalt": {
"status": "true",
"vendor": "test",
"phone": {
"$numberDouble": "11111111111"
},
"sim": "011111111111",
"salesnumb": "1222222222222222222"
}
},
"payment": {
"method": "Boleto",
"type": "Parcelado",
"installments": "5",
"billing_date": "15"
},
**"products": {
"items": {
"5d515979736802000415a561": {
"item": {
"_id": "5d515979736802000415a561",
"name_produto": "Product 1",
"resumo": "Minutos ilimitados,20GB + 2GB",
"price": "110",
"_image": "images/test.jpg"
},
"quantity": {
"$numberInt": "2"
},
"price": {
"$numberInt": "220"
}
},
"5d515aba736802000415a562": {
"item": {
"_id": "5d515aba736802000415a562",
"name_produto": "Product 2",
"resumo": "Minutos ilimitados,3GB + 1GB",
"price": "80",
"_image": "images/test.jpg"
},
"quantity": {
"$numberInt": "1"
},
"price": {
"$numberInt": "80"
}
},
"5d515dbf736802000415a564": {
"item": {
"_id": "5d515dbf736802000415a564",
"name_produto": "Product 3",
"resumo": "Minutos ilimitados,30GB + 3GB",
"price": "150",
"_image": "images/test.jpg"
},
"quantity": {
"$numberInt": "1"
},
"price": {
"$numberInt": "150"
}
}
},**
"totalItems": {
"$numberInt": "4"
},
"totalPrice": {
"$numberInt": "450"
}
},
"seller": {
"_id": {
"$oid": "5cd086787dc59921bcad94d8"
},
"name": "test"
}
}
I need output something like:
_id:Object
5d515979736802000415a561:{sum_price: 300, sum_quantity: 30 }
5d515aba736802000415a562:{sum_price: 500, sum_quantity: 60 }
5d515dbf736802000415a564:{sum_price: 600, sum_quantity: 70 }
Thanks so much!
So let's go with the OP ask to sum the individual product price and quantity. Stripping away the other fields which are not relevant to the ask, we arrive at something like this:
var r =
[
{
_id:0,
"products": {
"items": {
"5d515979736802000415a561": {
"quantity": 2,
"price": 220,
},
"5d515aba736802000415a562": {
"quantity": 1,
"price": 80
}
}
}
}
, {
_id:1,
"products": {
"items": {
"5d515979736802000415a561": { // deliberately same id as doc above but different quantity and price
"quantity": 3,
"price": 330
},
"5d515aba736802000415a562": { // same
"quantity": 2,
"price": 160
},
"5d515979736802000415ZZZZ": { // different than above; adds "third item"
"quantity": 4,
"price": 200
}
}
}
}
];
Note that the whole inner item field is basically not important, not the least of which it only contains the unit price, not the total price (amount) and quantity per product.
"5d515dbf736802000415a564": {
"item": {
"_id": "5d515dbf736802000415a564",
// etc
So now we employ the $objectToArray to turn the keys into rvals. That gives us something we can $group on, and so here is a solution:
db.foo.aggregate([
{$project: {X: {$objectToArray: "$products.items"}}}
,{$unwind: "$X"}
,{$group: {_id: "$X.k", tot_q: {$sum:"$X.v.quantity"}, tot_amt: {$sum:"$X.v.price"}} }
]);
which given the input above yields:
{ "_id" : "5d515979736802000415ZZZZ", "tot_q" : 4, "tot_amt" : 200 }
{ "_id" : "5d515aba736802000415a562", "tot_q" : 3, "tot_amt" : 240 }
{ "_id" : "5d515979736802000415a561", "tot_q" : 5, "tot_amt" : 550 }

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

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