MongoDb count elements of array in array by $size - mongodb

I need help to count the elements of an array when it is in another array.
My command, when I tried to select the second array is,
db.artysci.aggregate([
{
$project: {
_id:0,
nazwa: 1,
nazwisko: 1,
numberOfSongs: { "album": {$size: "$utwor"}}
}
}
] )
Grid:
db.artysci.insert({
imie: 'Laurie',
nazwisko: 'Adkins',
rok_debiutu: 2006,
kraj_pochodzenia: ['Wielka Brytania'],
gatunek: 'neo soul',
album: [{
tytul:"19",
rok_edycji:2007,
gatunek: 'pop',
typ_nosnika: 'CD',
utwor: [{
numer: 1,
tytul_utworu: 'Daydreamer',
dlugosc_trwania: 3.41
},
{
numer: 2,
tytul_utworu: 'Best for Last',
dlugosc_trwania: 4.19
},
{
numer: 3,
tytul_utworu: 'Chasing Pavements',
dlugosc_trwania: 3.31
}
]
}]
})
Output when counting by $size:"$album",
{
"nazwisko" : "Adkins",
"numberOfSongs" : {
"album" : NumberInt(3)
}
}
How can I count elements of an array in an array by $size?

You can achieve this using Map and then summing it up. It works..
db.artysci.aggregate({
"$project": {
_id: 0,
nazwa: 1,
nazwisko: 1,
"numberOfAlbums": { "$size": { $ifNull: ["$album", []] } },
"numberOfSongs": {
"$sum": {
"$map": {
"input": "$album",
"in": { "$size": { $ifNull: ["$$this.utwor", []] } }
}
}
}
}
})

#Kacper,
Here is the soultion for your second question.
Yes, you can achieve it in either way, using the above method or using unwind and do the average..
Lets see an example using unwind:
Without divide/second:
db.notifications.aggregate([
{ $unwind: "$album" },
{ $unwind: "$album.utwor" },
{
$group: {
_id: "$_id",
avgDuration: { $avg: "$album.utwor.dlugosc_trwania" }
}
},
]);
With divide/second:
db.notifications.aggregate([
{ $unwind: "$album" },
{ $unwind: "$album.utwor" },
{
$group: {
_id: "$_id",
avgDuration: { $avg: { $divide: ["$album.utwor.dlugosc_trwania", 60] } }
}
},
]);

You can use $unwind and $group to get the counts.
db.collection.aggregate([
{
$unwind: "$album"
},
{
$unwind: "$album.utwor"
},
{
$group: {
_id: 0,
total: {
$sum: 1
}
}
}
])
Play
If you need more information, add it to your question.

Related

MongoDB get size of unwinded array

I'm trying to return size of 'orders' and sum of 'item' values for each 'order' for each order from documents like the example document:
orders: [
{
order_id: 1,
items: [
{
item_id: 1,
value:100
},
{
item_id: 2,
value:200
}
]
},
{
order_id: 2,
items: [
{
item_id: 3,
value:300
},
{
item_id: 4,
value:400
}
]
}
]
I'm using following aggregation to return them, everything works fine except I can't get size of 'orders' array because after unwind, 'orders' array is turned into an object and I can't call $size on it since it is an object now.
db.users.aggregate([
{
$unwind: "$orders"
},
{
$project: {
_id: 0,
total_values: {
$reduce: {
input: "$orders.items",
initialValue: 0,
in: { $add: ["$$value", "$$this.value"] }
}
},
order_count: {$size: '$orders'}, //I get 'The argument to $size must be an array, but was of type: object' error
}
},
])
the result I expected is:
{order_count:2, total_values:1000} //For example document
{order_count:3, total_values:1500}
{order_count:5, total_values:2500}
I found a way to get the results that I wanted. Here is the code
db.users.aggregate([
{
$project: {
_id: 1, orders: 1, order_count: { $size: '$orders' }
}
},
{ $unwind: '$orders' },
{
$project: {
_id: '$_id', items: '$orders.items', order_count: '$order_count'
}
},
{ $unwind: '$items' },
{
$project: {
_id: '$_id', sum: { $sum: '$items.value' }, order_count: '$order_count'
}
},
{
$group: {
_id: { _id: '$_id', order_count: '$order_count' }, total_values: { $sum: '$sum' }
}
},
])
output:
{ _id: { _id: ObjectId("5dffc33002ef525620ef09f1"), order_count: 2 }, total_values: 1000 }
{ _id: { _id: ObjectId("5dffc33002ef525620ef09f2"), order_count: 3 }, total_values: 1500 }

Mongoose subquery

I have a collection that looks like below:
[
{
"orderNum": "100",
"createdTime": ISODate("2020-12-01T21:00:00.000Z"),
"amount": 100,
"memo": "100memo",
"list": [
1
]
},
{
"orderNum": "200",
"createdTime": ISODate("2020-12-01T21:01:00.000Z"),
"amount": 200,
"memo": "200memo",
"list": [
1,
2
]
},
{
"orderNum": "300",
"createdTime": ISODate("2020-12-01T21:02:00.000Z"),
"amount": 300,
"memo": "300memo"
},
{
"orderNum": "400",
"createdTime": ISODate("2020-12-01T21:03:00.000Z"),
"amount": 400,
"memo": "400memo"
},
]
and I'm trying to get the total amount of orders that were created before order# 300 (so order#100 and #200, total amount is 300).
Does anyone know how to get it via Mongoose?
You can use this one:
db.collection.aggregate([
{ $sort: { orderNum: 1 } }, // by default the order of documents in a collection is undetermined
{ $group: { _id: null, data: { $push: "$$ROOT" } } }, // put all documents into one document
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } }, // cut desired elementes from array
{ $unwind: "$data" }, // transform back to documents
{ $replaceRoot: { newRoot: "$data" } },
{ $group: { _id: null, total_amount: { $sum: "$amount" } } } // make summary
])
Actually it is not needed to $unwind and $group, so the shortcut would be this:
db.collection.aggregate([
{ $sort: { orderNum: 1 } },
{ $group: { _id: null, data: { $push: "$$ROOT" } } },
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } },
{ $project: { total_amount: { $sum: "$data.amount" } } }
])
But the answer from #turivishal is even better.
Update for additional field
{
$set: {
data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] },
memo: { $arrayElemAt: [ "$data.memo", { $indexOfArray: ["$data.orderNum", "300"] } ] }
}
}
or
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } },
{ $set: { memo: { $last: { "$data.memo" } } },
$match orderNum less than 300
$group by null and get totalAmount using $sum of amount
YourSchemaModel.aggregate([
{ $match: { orderNum: { $lt: "300" } } },
{
$group: {
_id: null,
totalAmount: { $sum: "$amount" }
}
}
])
Playground

MongoDb add $avg element of an array in an array by $map mapping

Problem is that I want to 'enter' array utwor and count average of dlugosc_utworu
How looks my code:
db.artysci.aggregate({
"$project": {
_id: 0,
nazwa: 1,
nazwisko: 1,
"numberOfSongs": {
"$sum": {
"$map": {
"input": "$album",
"in": { "$size": { $ifNull: ["$$this.utwor", []] } }
}
}
},
"avgSongTime":{
"$avg": {
"$map": {
"input": "utwor",
"in": { $ifNull: ["$$this.dlugosc_trwania", []] }
}
}
}
}
})
I want to make this avg of "dlugosc_trwania" who is located in utwor array in album array.
Grid:
db.artysci.insert({
imie: 'Nik',
nazwisko: 'Kershaw',
rok_debiutu: 1983,
kraj_pochodzenia: ['Wielka Brytania'],
gatunek: 'pop',
album: [{
tytul:"Human Racing",
rok_edycji:1990,
gatunek: 'trash metal',
typ_nosnika: 'CD',
utwor: [{
numer: 1,
tytul_utworu: 'Dancing Girls',
dlugosc_trwania: 3.46
},
{
numer: 2,
tytul_utworu: 'Wouldn’t It Be Good',
dlugosc_trwania: 4.32
},
{
numer: 3,
tytul_utworu: 'Drum Talk',
dlugosc_trwania: 3.10
},
{
numer: 4,
tytul_utworu: 'Bogart',
dlugosc_trwania: 4.38
}
]
}
})
Thanks to Faizul Hassan for your help and Yours, if you help me <3
Copy pasting the solution here from our conversation in another post..
Here is the soultion for your second question.
Lets see an example using unwind:
Without divide/second:
db.notifications.aggregate([
{ $unwind: "$album" },
{ $unwind: "$album.utwor" },
{
$group: {
_id: "$_id",
avgDuration: { $avg: "$album.utwor.dlugosc_trwania" }
}
},
]);
With divide/second:
db.notifications.aggregate([
{ $unwind: "$album" },
{ $unwind: "$album.utwor" },
{
$group: {
_id: "$_id",
avgDuration: { $avg: { $divide: ["$album.utwor.dlugosc_trwania", 60] } }
}
},
]);

MongoDB to return formatted object when no results can be found

I have the following stage in my MongoDB aggregation pipeline that returns the qty and sum of sales, which works fine:
{
$lookup: {
from: 'sales',
let: { part: '$_id' },
pipeline: [
{ $match: { $and: [{ $expr: { $eq: ['$partner', '$$part'] } }] } },
{ $group: { _id: null, qty: { $sum: 1 }, soldFor: { $sum: '$soldFor' } } },
{ $project: { _id: 0, qty: 1, soldFor: 1 } }],
as: 'sales'}},
{ $unwind: { path: '$sales', preserveNullAndEmptyArrays: true } },
{ $project: { _id: 1, sales: 1 }
}
However, if there are no sales, then the $project projection returns an empty sales object, but what I'd really like is it to return a completed object, but with 0 - like this:
{
sales: {
qty: 0,
soldFor: 0
}
}
You can use $cond operator here
{
"$project": {
"_id": 1,
"sales": {
"$cond": [
{ "$eq": [{ "$size": "$sales" }, 0] },
{
"sales": {
"qty": 0,
"soldFor": 0
}
},
"$sales"
]
}
}
}

Use $size on all documents in array MongoDB

Here's the structure part of my collection:
_id: ObjectId("W"),
names: [
{
number: 1,
list: ["A","B","C"]
},
{
number: 2,
list: ["B"]
},
{
number: 3,
list: ["A","C"]
}
...
],
...
I use this request:
db.publication.aggregate( [ { $match: { _id: ObjectId("54a1de90453d224e80f5fc60") } }, { $group: { _id: "$_id", SizeName: { $first: { $size: { $ifNull: [ "$names", [] ] } } }, names: { $first: "$names" } } } ] );
but I would now use $size in every documents of my names array.
Is it possible to get this result (where "sizeList" is the result of $size) :
_id: ObjectId("W"),
SizeName: 3,
names: [
{
SizeList: 3,
number: 1,
list: ["A","B","C"]
},
{
SizeList: 1,
number: 2,
list: ["B"]
},
{
SizeList: 2,
number: 3,
list: ["A","C"]
}
...
],
...
All you really want here is a $project stage and use $map to alter the contents of the array members:
db.names.aggregate([
{ "$project": {
"SizeName": { "$size": "$names" },
"names": { "$map": {
"input": "$names",
"as": "el",
"in": {
"SizeList": { "$size": "$$el.list" },
"number": "$$el.number",
"list": "$$el.list"
}
}}
}}
])
You can alternately process with $unwind and group it all back again, but that's kind of long winded when you have MongoDB 2.6 anyway.
This isn't necessarily the most efficient way, but I think it does what you're aiming to do:
db.publication.aggregate( [
{ $unwind: '$names' },
{ $unwind: '$names.list' },
{ $group: {
_id: { _id: "$_id", number: "$names.number" },
SizeList: { $sum: 1 },
list: { $push: "$names.list" }
}
},
{ $group: {
_id: "$_id._id",
names: {
$push: {
number: "$_id.number",
list: "$list",
SizeList: "$SizeList"
}
},
SizeName: {$sum: 1}
}
}
]);