Use $size on all documents in array MongoDB - 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}
}
}
]);

Related

Lodash `countBy` equivalent in MongoDB?

Let's say I have the input docs below:
[
{
"_id": "6225ca4052e7c226e2dd836d",
"data": [
"07",
"07",
"12",
"19",
"07",
"32"
]
},
{
"_id": "6225ca4052e7c226e2dd888f",
"data": [
"99",
"97",
"52",
"99",
"58",
"92"
]
}
]
I want to count the occurrences of every element in data string array per document. In JS, I can use countBy. How can I achieve the same using MongoDB Aggregation Framework?
I have tried to $reduce but MongoDB seems to not support assigning dynamic field to object.
{
$reduce: {
input: '$data',
initialValue: {},
in: { // assign `$$this` with count to `$$value`, but failed! }
}
}
Below is the desired output.
[
{
"_id": "6225ca4052e7c226e2dd836d",
"freqs": {
"12": 1,
"19": 1,
"32": 1,
"07": 3
}
},
{
"_id": "6225ca4052e7c226e2dd888f",
"freqs": {
"52": 1,
"58": 1,
"92": 1,
"97": 1,
"99": 2
}
}
]
db.collection.aggregate([
{
$match: {}
},
{
$unwind: "$data"
},
{
$group: {
_id: "$data",
c: { $sum: 1 },
id: { $first: "$_id" }
}
},
{
$group: {
_id: "$id",
data: { $push: { k: "$_id", v: "$c" } }
}
},
{
$set: {
data: { $arrayToObject: "$data" }
}
}
])
mongoplayground
db.collection.aggregate([
{
$set: {
data: {
$function: {
body: "function(d) {let obj = {}; d.forEach(e => {if(obj[e]==null) { obj[e]=1; }else{ obj[e]++; }}); return obj;}",
args: [
"$data"
],
lang: "js"
}
}
}
}
])
mongoplayground

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 count elements of array in array by $size

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.

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

Grouping and counting across documents?

I have a collection with documents similar to the following format:
{
departure:{name: "abe"},
arrival:{name: "tom"}
},
{
departure:{name: "bob"},
arrival:{name: "abe"}
}
And to get output like so:
{
name: "abe",
departureCount: 1,
arrivalCount: 1
},
{
name: "bob",
departureCount: 1,
arrivalCount: 0
},
{
name: "tom",
departureCount: 0,
arrivalCount: 1
}
I'm able to get the counts individually by doing a query for the specific data like so:
db.sched.aggregate([
{
"$group":{
_id: "$departure.name",
departureCount: {$sum: 1}
}
}
])
But I haven't figured out how to merge the arrival and departure name into one document along with counts for both. Any suggestions on how to accomplish this?
You should use a $map to split your doc into 2, then $unwind and $group..
[
{
$project: {
dep: '$departure.name',
arr: '$arrival.name'
}
},
{
$project: {
f: {
$map: {
input: {
$literal: ['dep', 'arr']
},
as: 'el',
in : {
type: '$$el',
name: {
$cond: [{
$eq: ['$$el', 'dep']
}, '$dep', '$arr']
}
}
}
}
}
},
{
$unwind: '$f'
}, {
$group: {
_id: {
'name': '$f.name'
},
departureCount: {
$sum: {
$cond: [{
$eq: ['$f.type', 'dep']
}, 1, 0]
}
},
arrivalCount: {
$sum: {
$cond: [{
$eq: ['$f.type', 'arr']
}, 1, 0]
}
}
}
}, {
$project: {
_id: 0,
name: '$_id.name',
departureCount: 1,
arrivalCount: 1
}
}
]