How to group query with multiple $cond? - mongodb

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

Related

Create a view from mongo collection

I have a mongo collection with records like
and so on.
The sample record in JSON format
[{
empId:'123',
empName:'Emp1',
shiftHours:'Regular'
},
{
empId:'123',
empName:'Emp1',
shiftHours:'Morning'
}
]
Basically an employee can work in regular shift(9am-6 pm) or morning shift (6 am-3 pm) or night shift (9pm-6 am). The hours are just example here but the idea is that the working hours are categorized in 3 shifts. I want to create a view with flat structure per employee like this
and so on.
I am trying to understand what's the best way to create such a flat view (coming from SQL background, I think a procedure/function has to be written) but not sure what's the best way to do so using No-Sql (Mongo db).
Any suggestions?
$group by empId and conditionally $sum by shiftHours.
db.collection.aggregate([
{
$group: {
_id: "$empId",
empName: {
$first: "$empName"
},
Morning: {
$sum: {
"$cond": {
"if": {
$eq: [
"$shiftHours",
"Morning"
]
},
"then": 1,
"else": 0
}
}
},
Regular: {
$sum: {
"$cond": {
"if": {
$eq: [
"$shiftHours",
"Regular"
]
},
"then": 1,
"else": 0
}
}
},
Evening: {
$sum: {
"$cond": {
"if": {
$eq: [
"$shiftHours",
"Evening"
]
},
"then": 1,
"else": 0
}
}
}
}
},
{
$set: {
Morning: {
$cond: [
{
$gt: [
"$Morning",
0
]
},
"Y",
"N"
]
},
Regular: {
$cond: [
{
$gt: [
"$Regular",
0
]
},
"Y",
"N"
]
},
Evening: {
$cond: [
{
$gt: [
"$Evening",
0
]
},
"Y",
"N"
]
}
}
}
])
Mongo Playground

MongoDb Create Aggregate Create query

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.

MongoDB: add field with conditional number of elements

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 }

Difference between queries with grouped

I'm new to mongodb, and I have 2 queries:
frist:
db.movies.aggregate([
{ "$match": {
$and : [
{ "imdb.rating": { $lt: 7 }},
{$and: [ {"genres": { "$ne": "Crime" } }, {"genres": { "$ne": "Horror" } }]},
{$and: [ {"languages": { "$eq": "English" } }, {"languages": { "$eq": "Japanese" } }]},
{$or: [ {"rated": { "$eq": "PG" } }, {"rated": { "$eq": "G" } }]}
]
}
}
]).itcount()
The result its: 23
And now with this:
db.movies.aggregate([
{ "$match": {
"imdb.rating": { $lt: 7 },
$and: [ {"genres": { "$ne": "Crime" } }, {"genres": { "$ne": "Horror" } }],
$or: [ {"rated": { "$eq": "PG" } }, {"rated": { "$eq": "G" } }],
$and: [ {"languages": { "$eq": "English" } }, {"languages": { "$eq": "Japanese" } }]
}
}
]).itcount()
The result its 25,
But now I can't understand what is the difference between two queries, can help with this?
There is no functional difference. $match implicitly is using $and when you provide more than one expression and a simple key:value expression is shorthand for key: {$eq: value} e.g.
$match: {a:3, b:"buzz"}
is shorthand for:
$match: {$and: [{a:{$eq:3}}, {b:{$eq:"buzz"}} ] }

Combine three different Aggregate into a single one

I have three different Aggregations that I would like to combine to one.
objsCount = self.db.bidding.aggregate([
{ "$group": {
"_id": "$bid_item",
"number_of_objects": { "$sum": 1 }
}}
])
objsPrice = self.db.bidding.aggregate([
{"$match": {"$and" : [{"price": {"$ne":"null"}},{"users":{"$ne":"null"}}]}},
{ "$group": {
"_id": "$bid_item",
"total_price": { "$sum": '$price' },
"total_users": { "$sum": '$users' }
}}
])
objsHigestBid = self.db.bidding.aggregate([
{"$match": {"$and" : [{"highestBid": {"$ne":"null"}},{"users":{"$ne":"null"}}]}},
{ "$group": {
"_id": "$bid_item",
"total_number_of_users_after_bid": { "$sum": '$highestBid' }
}}
])
The output I'm looking for is:
[{ _id : '',
number_of_objects : '',
total_price : '',
total_users : '',
total_number_of_users_after_bid : ''
},...]
How could I change my "aggregates" to one single "aggregate" that make this possible?
With a single $group pipeline, you can take advantage of the $cond operator in the pipeline step to evaluate the counts based on the logic specified, something like the following:
db.bidding.aggregate([
{
"$group": {
"_id": "$bid_item",
"number_of_objects": { "$sum": 1 },
"total_price": {
"$sum": {
"$cond": [ { "$ne": [ "$price", "null" ] }, "$price", 0 ]
}
},
"total_users": {
"$sum": {
"$cond": [ { "$ne": [ "$users", "null" ] }, "$users", 0 ]
}
},
"total_number_of_users_after_bid": {
"$sum": {
"$cond": [
{
"$and": [
{ "$ne": [ "$highestBid", "null" ] },
{ "$ne": [ "$users", "null" ] }
]
},
"$highestBid", 0
]
}
}
}
}
])