How to group mongo result for specific keys? - mongodb

I've a collection that has this documents:
{name: "x", code: 1},
{name: "x", code: 2},
{name: "x", code: 3},
{name: "x", code: 1}
How can i group and count the keys that are equal to 1 and what is not equal to 1?
I manage to group by the code but the result comes separately for 1, 2 and 3
aggregate([
{'$match': {'name': "x"}},
{'$group': {'_id': '$code', 'total': {'$sum': 1}}},
{'$sort': {'_id': 1}}
])

You can conditionally sum the counter.
aggregate([
{
$group: {
_id: null,
key1: { // Count of all documents with keys(code) that are equal to 1
'$sum': {
'$cond': {
if: { '$eq': [1, '$code'] },
then: 1,
else: 0
}
}
},
otherKeys: { // Count of all other documents with keys not equal to 1
'$sum': {
'$cond': {
if: { '$eq': [1, '$code'] },
then: 0,
else: 1
}
}
}
},
}
])
This would output a result document like this:
{ "_id" : null, "key1" : *, "otherKeys" : * }
Where key1 is the count of all documents with keys equal to 1, while otherKeys is the count of all documents with keys not equal to 1.

Aggregation with $facet allows you to query on same set of documents in multiple facets. You can the desired result with this query:
db.codes.aggregate( [
{ $project: { code: { $eq: [ '$code', 1 ] } } },
{ $facet: {
code_is_1: [
{ $match: { code: true } },
{ $count: "Code equals 1" },
],
code_not_1: [
{ $match: { code: false } },
{ $count: "Code not equals 1" },
],
} },
{ $project: { Counts: { $concatArrays: [ "$code_is_1", "$code_not_1" ] } } },
] )
The result will look like this with the test documents you posted: { "Counts" : [ { "Code equals 1" : 2 }, { "Code not equals 1" : 2 } ] }

Related

MongoDB aggregations. Get value differences

I've been struggling with mongo trying to find a solution to show the differences between values.
I have values like this:
[
{val: 1},
{val: 4},
{val: 7},
{val: 8},
{val: 11}
]
And I want to receive something like this:
[
{diff: 3},
{diff: 3},
{diff: 1},
{diff: 3}
]
Every value is evaluated by taking the next one (4) and subtracting the previous one (1). After all this, we receive 3 in output, which is located in the second list as the first item.
Is it possible to achieve it using MongoDB aggregations?
You need to group them into array, calculate diff and flatten again.
Pseudocode
//We $group here all values
var _data = [{val: 1}, {val: 4}, ..., {val: 11}];
//With $range operator we get nÂș of items
// We ensure even items, since odd items will return null as last item
var idx = [0, 1, 2, ..., n];
//Here we store diff items with $map operator
var data = [];
//$map
for (var i in idx) {
data[i] = _data[i+1] - _data[i];
}
//$unwind
{data:[0]}, {data[1]}, {data[2]}, ...
//$replaceRoot
{
data:{ {
diff : 3 --> diff : 3
} }
}
Add these steps into your pipeline:
db.collection.aggregate([
{
$group: {
_id: null,
data: { $push: "$$ROOT" }
}
},
{
$addFields: {
data: {
$map: {
input: {
$range: [
0,
{
$subtract: [
{ $size: "$data" },
{ $mod: [ { $size: "$data" }, 2 ] }
]
},
1
]
},
as: "idx",
in: {
diff: {
$subtract: [
{
$arrayElemAt: [
"$data.val",
{
$add: [ "$$idx", 1 ]
}
]
},
{
$arrayElemAt: [ "$data.val", "$$idx" ]
}
]
}
}
}
}
}
},
{
$unwind: "$data"
},
{
$replaceRoot: {
newRoot: "$data"
}
}
])
MongoPlayground

MongoDb Exists per column

Running an aggregation such as the following:
[
{
"$match":{
"datasourceName":"Startup Failures",
"sheetName":"Data",
"Cost":{
"$exists":true
},
"Status":{
"$exists":true
}
}
},
{
"$group":{
"Count of Cost":{
"$sum":1
},
"Count of Status":{
"$sum":1
},
"_id":null
}
},
{
"$project":{
"Count of Cost":1,
"Count of Status":1
}
}
]
The result of the exists filters actually filters out the whole documents where "Cost" or "Status" do not exist. Such that the projection (Count) of both Cost and Status are the same. I don't want to filter the whole document only the individual columns such that the projection I get is the number of documents where Cost exists (Count of Cost) and the other projection is the number of documents where Status exists. In the case of my data these would give two separate numbers.
I have an aggregation using $facet; this allows do queries in parallel for each document pass. So, we query and count the Cost and Status as two facets of the same query.
db.test.aggregate( [
{
$match: { fld1: "Data" }
},
{
$facet: {
cost: [
{ $match: { cost: { $exists: true } } },
{ $count: "count" }
],
status: [
{ $match: { status: { $exists: true } } },
{ $count: "count" }
],
}
},
{
$project: {
costCount: { $arrayElemAt: [ "$cost.count" , 0 ] },
statusCount: { $arrayElemAt: [ "$status.count" , 0 ] }
}
}
] )
I get a result of { "costCount" : 4, "statusCount" : 3 }, using the following documents:
{ _id: 1, fld1: "Data", cost: 12, status: "Y" },
{ _id: 2, fld1: "Data", status: "N" },
{ _id: 3, fld1: "Data" },
{ _id: 4, fld1: "Data", cost: 90 },
{ _id: 5, fld1: "Data", cost: 44 },
{ _id: 6, fld1: "Data", cost: 235, status: "N" },
{ _id: 9, fld1: "Stuff", cost: 0, status: "Y" }
NOTE: Here is a similar query using the facets: MongoDB Custom sorting on two fields.

MongoDB. Aggregate the sum of two arrays sizes

With MongoDB 3.4.10 and mongoose 4.13.6 I'm able to count sizes of two arrays on the User model:
User.aggregate()
.project({
'_id': 1,
'leftVotesCount': { '$size': '$leftVoted' },
'rightVotesCount': { '$size': '$rightVoted' }
})
where my Users are (per db.users.find())
{ "_id" : ObjectId("5a2b21e63023c6117085c240"), "rightVoted" : [ 2 ],
"leftVoted" : [ 1, 6 ] }
{ "_id" : ObjectId("5a2c0d68efde3416bc8b7020"), "rightVoted" : [ 2 ],
"leftVoted" : [ 1 ] }
Here I'm getting expected result:
[ { _id: '5a2b21e63023c6117085c240', leftVotesCount: 2, rightVotesCount: 1 },
{ _id: '5a2c0d68efde3416bc8b7020', leftVotesCount: 1, rightVotesCount: 1 } ]
Question. How can I get a cumulative value of leftVotesCount and rightVotesCount data? I tried folowing:
User.aggregate()
.project({
'_id': 1,
'leftVotesCount': { '$size': '$leftVoted' },
'rightVotesCount': { '$size': '$rightVoted' },
'votesCount': { '$add': [ '$leftVotesCount', '$rightVotesCount' ] },
'votesCount2': { '$sum': [ '$leftVotesCount', '$rightVotesCount' ] }
})
But votesCount is null and votesCount2 is 0 for both users. I'm expecting votesCount = 3 for User 1 and votesCount = 2 for User 2.
$leftVotesCount, $rightVotesCount become available only on the next stage. Try something like:
User.aggregate()
.project({
'_id': 1,
'leftVotesCount': { '$size': '$leftVoted' },
'rightVotesCount': { '$size': '$rightVoted' }
})
.project({
'_id': 1,
'leftVotesCount': 1,
'rightVotesCount': 1
'votesCount': { '$add': [ '$leftVotesCount', '$rightVotesCount' ] },
'votesCount2': { '$sum': [ '$leftVotesCount', '$rightVotesCount' ] }
})
You can't reference the project variables created in the same project stage.
You can wrap the variables in a $let expression.
User.aggregate().project({
"$let": {
"vars": {
"leftVotesCount": {
"$size": "$leftVoted"
},
"rightVotesCount": {
"$size": "$rightVoted"
}
},
"in": {
"votesCount": {
"$add": [
"$$leftVotesCount",
"$$rightVotesCount"
]
},
"leftVotesCount": "$$leftVotesCount",
"rightVotesCount": "$$rightVotesCount"
}
}
})
It turned out that $add supports nested expressions, so I was able to solve the issue by excluding intermediate variables:
User.aggregate().project({
'_id': 1,
'votesCount': { '$add': [ { '$size': '$leftVoted' }, { '$size': '$rightVoted' } ] }
});
// [ {_id: '...', votesCount: 3}, {_id: '...', votesCount: 2} ]

mongodb - Filter object where all elements from nested array match the condition

Supose a database containing something like that
{
"grades":[
{
"grade":"A",
"score":2
},
{
"grade":"A",
"score":6
},
],
"name":"Morris Park Bake Shop"
},
{
"grades":[
{
"grade":"A",
"score":8
},
{
"grade":"B",
"score":23
}
],
"name":"Wendy'S"
}
How can I apply a filter that will just return the restaurants where ALL grades are "A"?
If I try
db.restaurants.find({ "grades.grade" : "A" } ), the way it works is that it search for ANY grade inside my element.
I tried using aggregate with unwind to, but it do the same thing, it opens grades, filter, and returns any match of restaurant...
In your situation I would do something like this :
db.getCollection('test').aggregate([
{$unwind:"$grades"},
{ $group: {
_id: '$_id',
grades : { $first: '$grades' },
all_grades: { $sum: 1 },
all_grades_that_match: { $sum: { $cond: [ { $eq: [ '$grades.grade', "A" ] }, 1, 0 ] } },
name: { $first: '$name' }
}},
{ $project: {
_id: 1,
name: 1,
grades: 1,
arrays_equal: { $cond: [ { $eq: [ '$all_grades', '$all_grades_that_match' ] }, 1, 0 ] }
}},
{ $match: { 'arrays_equal' : 1 } }
])
The group operation will count the total number of grades and the number of grades that match you query, the projection will compare those two results to see if they are equal, finally, the match operation will only keep the ones where arrays_equal is true

mongodb aggregation query for field value length's sum

Say, I have following documents:
{name: 'A', fav_fruits: ['apple', 'mango', 'orange'], 'type':'test'}
{name: 'B', fav_fruits: ['apple', 'orange'], 'type':'test'}
{name: 'C', fav_fruits: ['cherry'], 'type':'test'}
I am trying to query to find the total count of fav_fruits field on overall documents returned by :
cursor = db.collection.find({'type': 'test'})
I am expecting output like:
cursor.count() = 3 // Getting
Without much idea of aggregate, can mongodb aggregation framework help me achieve this in any way:
1. sum up the lengths of all 'fav_fruits' field: 6
and/or
2. unique 'fav_fruit' field values = ['apple', 'mango', 'orange', 'cherry']
You need to $project your document after the $match stage and use the $size operator which return the number of items in each array. Then in the $group stage you use the $sum accumulator operator to return the total count.
db.collection.aggregate([
{ "$match": { "type": "test" } },
{ "$project": { "count": { "$size": "$fav_fruits" } } },
{ "$group": { "_id": null, "total": { "$sum": "$count" } } }
])
Which returns:
{ "_id" : null, "total" : 6 }
To get unique fav_fruits simply use .distinct()
> db.collection.distinct("fav_fruits", { "type": "test" } )
[ "apple", "mango", "orange", "cherry" ]
Do this to get just the number of fruits in the fav_fruits array:
db.fruits.aggregate([
{ $match: { type: 'test' } },
{ $unwind: "$fav_fruits" },
{ $group: { _id: "$type", count: { $sum: 1 } } }
]);
This will return the total number of fruits.
But if you want to get the array of unique fav_fruits along with the total number of elements in the fav_fruits field of each document, do this:
db.fruits.aggregate([
{ $match: { type: 'test' } },
{ $unwind: "$fav_fruits" },
{ $group: { _id: "$type", count: { $sum: 1 }, fav_fruits: { $addToSet: "$fav_fruits" } } }
])
You can try this. It may helpful to you.
db.collection.aggregate([{ $match : { type: "test" } }, {$group : { _id : null, count:{$sum:1} } }])