I have next simplified collection
[
{
"key": 1,
"array": [
{ "check": true },
{ "check": false },
{ "check": true }
]
},
{
"key": 2
}
]
I want to add field "count" with number of elements of array with "check"=true, so I expect next result
{
"key": 1,
"array": [
{ "check": true },
{ "check": false },
{ "check": true }
],
"count":2,
},
{
"key": 2,
"count": 0
}
]
I have next query ( it is aggregation, because actually it one of stages of pipeline)
db.collection.aggregate([
{
"$addFields": {
"count": {
"$sum": {
"$cond": {
"if": {
"$eq": ["$array.check",true],
},
"then": 1,
"else": 0,
}
}
},
}
}
])
But I always get count=0.
Can you help me to find error in my query?
Here mongo playground
Instead of using $sum, you can use $filter to filter only the array with "check"=true, then check the size of the resulting array using $size.
db.collection.aggregate([
{
"$addFields": {
"count": {
"$size": {
"$filter": {
"input": { "$ifNull": ["$array", []] }, // default empty array if array is does not exist
"cond": "$$this.check" // only keep the truthy check value
}
}
}
}
}
])
Mongo Playground
Alternatively, if you want to use $sum, you could also map the array to an array of 0 and 1 according to the check value, using $map
db.collection.aggregate([
{
"$addFields": {
"count": {
"$sum": {
"$map": {
"input": { "$ifNull": ["$array", []] },
"in": {
"$cond": ["$$this.check", 1, 0]
}
}
}
}
}
}
])
Mongo Playground
Here is how to achieve this using $reduce
db.collection.aggregate([
{
"$addFields": {
"count": {
"$sum": {
$reduce: {
input: "$array",
initialValue: 0,
in: {
$sum: [
"$$value",
{
$cond: [
"$$this.check",
1,
0
]
}
]
}
}
}
}
}
}
])
Mongo Playground
Using $reduce aggregation array expression operator,
db.test.aggregate( [
{
$addFields: {
count: {
$reduce: {
input: { $ifNull: [ "$array", [] ] },
initialValue: 0,
in: {
$cond: [ { $eq: [ "$$this.check", true ] },
{ $add: [ "$$value", 1 ] },
"$$value"
]
}
}
}
}
}
] )
gets a result as follows:
{
"_id" : ObjectId("5f0c7b691c7c98bb49fd2b50"),
"key" : 1,
"array" : [
{
"check" : true
},
{
"check" : false
},
{
"check" : true
}
],
"count" : 2
}
{ "_id" : ObjectId("5f0c7b691c7c98bb49fd2b51"), "key" : 2, "count" : 0 }
Related
I have 3 table users,shifts,temporaryShifts,
shifts:[{_id:ObjectId(2222),name:"Morning"},{_id:ObjectId(454),name:"Night"}]
users:[{_id:ObjectId(123),name:"Albert",shift_id:ObjectId(2222)}]
temporaryShifts:[
{_id:2,userId:ObjectId(123),shiftId:ObjectId(454),type:"temporary",date:"2020-02-01"},
{_id:987,userId:ObjectId(123),shiftId:ObjectId(454),type:"temporary",date:"2020-02-03"},
{_id:945,userId:ObjectId(123),shiftId:ObjectId(454),type:"temporary",date:"2020-02-08"},
{_id:23,userId:ObjectId(123),shiftId:ObjectId(454),date:"2020-02-09"}]
i want to make a mongoose aggregate query then give me result :
get result between two dates for example :2020-02-01 2020-02-05,
resullts is :
[
{_id:ObjectId(123),name:"Albert",shift:[
{_id:2,shiftId:ObjectId(454),type:"temporary",date:"2020-02-01"},
{_id:2,shiftId:ObjectId(2222),type:"permanent",date:"2020-02-02"},
{_id:2,shiftId:ObjectId(454),type:"temporary",date:"2020-02-03"},
{_id:2,shiftId:ObjectId(2222),type:"permanent",date:"2020-02-04"},
{_id:2,shiftId:ObjectId(2222),type:"permanent",date:"2020-02-05"},
]}
]
in result type temporary mean selected date in table temporaryShift document available else type permanent
MongoPlayGround You Can edit
You can first project a date range array using $range, in your example it will be like [2020-02-01, 2020-02-02, 2020-02-03, 2020-02-04, 2020-02-05], then you can use the array to perform $lookup
db.users.aggregate([
{
$limit: 1
},
{
"$addFields": {
"startDate": ISODate("2020-02-01"),
"endDate": ISODate("2020-02-05")
}
},
{
"$addFields": {
"dateRange": {
"$range": [
0,
{
$add: [
{
$divide: [
{
$subtract: [
"$endDate",
"$startDate"
]
},
86400000
]
},
1
]
}
]
}
}
},
{
"$addFields": {
"dateRange": {
$map: {
input: "$dateRange",
as: "increment",
in: {
"$add": [
"$startDate",
{
"$multiply": [
"$$increment",
86400000
]
}
]
}
}
}
}
},
{
"$unwind": "$dateRange"
},
{
"$project": {
"name": 1,
"shiftId": 1,
"dateCursor": "$dateRange"
}
},
{
"$lookup": {
"from": "temporaryShifts",
"let": {
dateCursor: "$dateCursor",
shiftId: "$shiftId"
},
"pipeline": [
{
"$addFields": {
"parsedDate": {
"$dateFromString": {
"dateString": "$date",
"format": "%Y-%m-%d"
}
}
}
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$$dateCursor",
"$parsedDate"
]
}
]
}
}
}
],
"as": "temporaryShiftsLookup"
}
},
{
"$unwind": {
path: "$temporaryShiftsLookup",
preserveNullAndEmptyArrays: true
}
},
{
$project: {
shiftId: 1,
type: {
"$ifNull": [
"$temporaryShiftsLookup.type",
"permanent"
]
},
date: "$dateCursor"
}
}
])
Here is the Mongo Playground for your reference.
hello all i'm working with a MongoDB database where each data row is like:
{
"_id" : ObjectId("5cf12696e81744d2dfc0000c"),
"contributor": "user1",
"title": "Title 1",
"userhasRate" : [
"51",
"52",
],
"ratings" : [
4,
3
],
}
and i need to change it to be like:
{
"_id" : ObjectId("5cf12696e81744d2dfc0000c"),
"contributor": "user1",
"title": "Title 1",
rate : [
{userhasrate: "51", value: 4},
{userhasrate: "52", value: 3},
]
}
I already try using this method,
db.getCollection('contens').aggregate([
{ '$group':{
'rates': {$push:{ value: '$ratings', user: '$userhasRate'}}
}
}
]);
and my result become like this
{
"rates" : [
{
"value" : [
5,
5,
5
],
"user" : [
"51",
"52",
"53"
]
}
]
}
Can someone help me to solve my problem,
Thank you
You can use $arrayToObject and $objectToArray inside $map to achieve the required output.
db.collection.aggregate([
{
"$project": {
"rate": {
"$map": {
"input": {
"$objectToArray": {
"$arrayToObject": {
"$zip": {
"inputs": [
"$userhasRate",
"$ratings"
]
}
}
}
},
"as": "el",
"in": {
"userhasRate": "$$el.k",
"value": "$$el.v"
}
}
}
}
}
])
Alternative Method
If userhasRate contains repeated values then the first solution will not work. You can use arrayElemAt and $map along with $zip if it contains repeated values.
db.collection.aggregate([
{
"$project": {
"rate": {
"$map": {
"input": {
"$zip": {
"inputs": [
"$userhasRate",
"$ratings"
]
}
},
"as": "el",
"in": {
"userhasRate": {
"$arrayElemAt": [
"$$el",
0
]
},
"value": {
"$arrayElemAt": [
"$$el",
1
]
}
}
}
}
}
}
])
Try below aggregate, first of all you used group without _id that grouped all the JSONs in the collection instead set it to "$_id" also you need to create 2 arrays using old data then in next project pipeline concat the arrays to get desired output:
db.getCollection('contens').aggregate([
{
$group: {
_id: "$_id",
rate1: {
$push: {
userhasrate: {
$arrayElemAt: [
"$userhasRate",
0
]
},
value: {
$arrayElemAt: [
"$ratings",
0
]
}
}
},
rate2: {
$push: {
userhasrate: {
$arrayElemAt: [
"$userhasRate",
1
]
},
value: {
$arrayElemAt: [
"$ratings",
1
]
}
}
}
}
},
{
$project: {
_id: 1,
rate: {
$concatArrays: [
"$rate1",
"$rate2"
]
}
}
}
])
I'm trying to work out exactly how to achieve an aggregation, I could manually unwind and group back together at the end, but I'm sure I should be able to achieve this in a more concise way so I wanted to throw it out as I'm getting stuck.
My document structure (skipping out the un-interesting bits) looks like:
{
_id: ObjectId,
panels: [
{
visConfig: {
dataConfig: {
columns: [
{ element: "DX" },
{ element: "SE" },
]
}
}
},
{
visConfig: {
dataConfig: {
columns: [
{ element: "AB" },
{ element: "XY" },
]
}
}
}
]
}
What I want to do is calculate a percentage of the element overlaps with a given set to be provided. So for example for the document shown it would produce 25% for the set ["DX"] or 50% for the set ["DX", "AB"].
So I've tried a few things, I think I've settled on the nearest so far as:
$project: {
_id: 1,
total: { $sum: { $size: "$panels.visConfig.dataConfig.columns" } }
}
But I'm getting an error here which I don't understand:
The argument to $size must be an array, but was of type: missing
Then I'm also having issues with my conditional aggregation which seems to be returning 0 for all of the element values.
{
_id: 1,
"panels.visConfig.dataConfig.columns.element": {
$sum: {
$cond: [{
$setIsSubset: [
["DX"], ["$panels.visConfig.dataConfig.columns.element"]
]
}, 1, 0 ],
}
},
}
You can try below aggregation in 3.4 version.
db.colname.aggregate([
{"$project":{
"_id":1,
"total":{
"$reduce":{
"input":"$panels.visConfig.dataConfig.columns.element",
"initialValue":0,
"in":{"$add":["$$value",{"$size":"$$this"}]}
}},
"match":{
"$sum":{
"$map":{
"input":"$panels.visConfig.dataConfig.columns.element",
"in":{
"$size":{
"$setIntersection":[["DX","AB"],"$$this"]
}
}
}
}
}
}},
{"$project":{
"_id":1,
"percent":{"$multiply":[{"$divide":["$match","$total"]}, 100]}
}}])
Update - You can perform both match and total calculations in $reduce pipeline.
db.colname.aggregate([
{"$project":{
"_id":1,
"stats":{
"$reduce":{
"input":"$panels.visConfig.dataConfig.columns.element",
"initialValue":{"total":0,"match":0},
"in":{
"total":{"$add":["$$value.total",{"$size":"$$this"}]},
"match":{"$add":["$$value.match",{"$sum":{"$map":{"input":"$$this","in":{"$cond":[{"$in":["$$this", ["DX","AB"]] }, 1, 0]}}}}]}
}
}}
}},
{"$project":{
"_id":1,
"percent":{"$multiply":[{"$divide":["$stats.match","$stats.total"]}, 100]}
}}])
You can use $map + $reduce to get an array of all element values and then using $divide you can divide $filter-ed $size by total $size:
db.col.aggregate([
{
$project: {
elements: {
$reduce: {
input: {
$map: {
input: "$panels",
as: "panel",
in: "$$panel.visConfig.dataConfig.columns.element"
}
},
initialValue: [],
in: { $concatArrays: [ "$$this", "$$value" ] }
}
}
}
},
{
$project: {
percentage: {
$divide: [
{
$size: {
$filter: {
input: "$elements",
as: "element",
cond: {
$in: [
"$$element",
[ "AB", "XY" ] // your input here
]
}
}
}
},
{ $size: "$elements" }
]
}
}
}
])
Well, there are couple of ways to do this, but I these two pipelines show how I would do it.
var values = ["DX", "KL"]
First approach
[
{
"$project": {
"percent": {
"$let": {
"vars": {
"allsets": {
"$reduce": {
"input": "$panels.visConfig.dataConfig.columns",
"initialValue": [],
"in": {
"$concatArrays": [ "$$this.element", "$$value" ]
}
}
}
},
"in": {
"$multiply": [
{
"$divide": [
{
"$size": {
"$setIntersection": [ "$$allsets", values ]
}
},
{ "$size": "$$allsets" }
]
},
100
]
}
}
}
}
}
]
Second approach same idea here but, using one pipeline stage.
[
{
"$project": {
"percent": {
"$multiply": [
{
"$divide": [
{
"$sum": {
"$map": {
"input": "$panels.visConfig.dataConfig.columns.element",
"in": {
"$size": {
"$setIntersection": [ values, "$$this" ]
}
}
}
}
},
{
"$reduce": {
"input": "$panels.visConfig.dataConfig.columns.element",
"initialValue": 0,
"in": {
"$add": [ "$$value", { "$size": "$$this" } ]
}
}
}
]
},
100
]
}
}
}
]
I have documents like:
{
"platform":"android",
"install_date":20151029
}
platform - can have one value from [android|ios|kindle|facebook ] .
install_date - there are many install_dates
There are also many fields.
Aim : I am calculating installs per platform on particular date.
So I am using group by in aggregation framework and make counts by platform. Document should look like like:
{
"install_date":20151029,
"platform" : {
"android":1000,
"ios": 2000,
"facebook":1500
}
}
I have done like:
db.collection.aggregate([
{
$group: {
_id: { platform: "$platform",install_date:"$install_date"},
count: { "$sum": 1 }
}
},
{
$group: {
_id: { install_date:"$_id.install_date"},
platform: { $push : {platform :"$_id.platform", count:"$count" } }
}
},
{
$project : { _id: 0, install_date: "$_id.install_date", platform: 1 }
}
])
which Gives document like:
{
"platform": [
{
"platform": "facebook",
"count": 1500
},
{
"platform": "ios",
"count": 2000
},
{
"platform": "android",
"count": 1000
}
],
"install_date": 20151027
}
Problem:
Projecting array to single object as "platform"
With MongoDb 3.4 and newer, you can leverage the use of $arrayToObject operator to get the desired result. You would need to run the following aggregate pipeline:
db.collection.aggregate([
{ "$group": {
"_id": {
"date": "$install_date",
"platform": { "$toLower": "$platform" }
},
"count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.date",
"counts": {
"$push": {
"k": "$_id.platform",
"v": "$count"
}
}
} },
{ "$addFields": {
"install_date": "$_id",
"platform": { "$arrayToObject": "$counts" }
} },
{ "$project": { "counts": 0, "_id": 0 } }
])
For older versions, take advantage of the $cond operator in the $group pipeline step to evaluate the counts based on the platform field value, something like the following:
db.collection.aggregate([
{ "$group": {
"_id": "$install_date",
"android_count": {
"$sum": {
"$cond": [ { "$eq": [ "$platform", "android" ] }, 1, 0 ]
}
},
"ios_count": {
"$sum": {
"$cond": [ { "$eq": [ "$platform", "ios" ] }, 1, 0 ]
}
},
"facebook_count": {
"$sum": {
"$cond": [ { "$eq": [ "$platform", "facebook" ] }, 1, 0 ]
}
},
"kindle_count": {
"$sum": {
"$cond": [ { "$eq": [ "$platform", "kindle" ] }, 1, 0 ]
}
}
} },
{ "$project": {
"_id": 0, "install_date": "$_id",
"platform": {
"android": "$android_count",
"ios": "$ios_count",
"facebook": "$facebook_count",
"kindle": "$kindle_count"
}
} }
])
In the above, $cond takes a logical condition as it's first argument (if) and then returns the second argument where the evaluation is true (then) or the third argument where false (else). This makes true/false returns into 1 and 0 to feed to $sum respectively.
So for example, if { "$eq": [ "$platform", "facebook" ] }, is true then the expression will evaluate to { $sum: 1 } else it will be { $sum: 0 }
I want to query like below, but this contains only one $cond.
How to query with two $cond?
collection.aggregate(
{
$match : {
'_id' : {$in:ids}
}
},
{
$group: {
_id: '$someField',
...
count: {$sum: { $cond: [ { $eq: [ "$otherField", false] } , 1, 0 ] }}
}
},
function(err, result){
...
}
);
You want to use a compound expression inside {$cond:[]} - something like:
collection.aggregate(
{
$match : {
'_id' : {$in:ids}
}
},
{
$group: {
_id: '$someField',
...
count: {$sum: { $cond: [ {$and : [ { $eq: [ "$otherField", false] },
{ $eq: [ "$anotherField","value"] }
] },
1,
0 ] }}
}
},
function(err, result){
...
}
);
The $and operator is documented here: http://docs.mongodb.org/manual/reference/operator/aggregation/#boolean-operators
you can add multiple $cond and multiple criterias inside $cond like this
`
collection.aggregate(
[
{
"$match": {
//matching criteria
}
},
{
"$project": {
"service": {
"$cond": {
"if": {
"$eq": [
"$foo",
"bar"
]
},
"then": "return string1",
"else": {
"$cond": {
"if": {
"$eq": [
"$foo",
"bar"
]
},
"then": "return string2",
"else": {
"$cond": {
"if": {
"$or": [
{
"$eq": [
"$foo",
"bar1"
]
},
{
"$eq": [
"$foo",
"bar2"
]
}
]
},
"then": "return string3",
"else": "$foo"
}
}
}
}
}
},
"_id": 0
}
}
]
)
`