Create a view from mongo collection - mongodb

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

Related

Create a flat materialized view from a mongo collection

In mongo db, I have the input collection
Collection name - dirPermission and sample record
[
{
dirId: "1",
dirName: "firstDir",
usersRead: [
"user1",
"user2"
],
userWrite: [
"user2",
"user3"
]
}
]
and I want to create a materialized view like the below
[{
dirId:'1',
dirName:'firstDir',
userId:'user1',
canRead:'Y',
canWrite:'N'
},
{
dirId:'1',
dirName:'firstDir',
userId:'user2',
canRead:'Y',
canWrite:'Y'
},
{
dirId:'1',
dirName:'firstDir',
userId:'user3',
canRead:'N',
canWrite:'Y'
}]
Again since my back ground is more of SQL + Java, I am struggling to find an answer using mongodb and any pointers will be helpful.
You can use $setUnion to create a distinct set of users. $unwind it to create documents and use $cond to set canRead and canWrite.
db.collection.aggregate([
{
"$addFields": {
"allUsers": {
$setUnion: [
"$usersRead",
"$userWrite"
]
}
}
},
{
"$unwind": "$allUsers"
},
{
"$project": {
dirId: 1,
dirName: 1,
userId: "$allUsers",
canRead: {
"$cond": {
"if": {
"$in": [
"$allUsers",
"$usersRead"
]
},
"then": "Y",
"else": "N"
}
},
canWrite: {
"$cond": {
"if": {
"$in": [
"$allUsers",
"$userWrite"
]
},
"then": "Y",
"else": "N"
}
}
}
}
])
Mongo Playground

mongodb - Subtracts two numbers total to return the difference

Consider I have the following collection:
[
{
"total": 48.0,
"status": "CO"
},
{
"total": 11.0,
"status": "CA"
},
{
"total": 15916.0,
"status": "PE"
}
]
I need to realize the difference of PE status - (CO + CA).
The expected result is:
{
"_id" : null,
"total" : 15857.0
}
Use $switch to cater for different cases for your sum. Use $subtract to flip the sign for the partial sum.
db.collection.aggregate([
{
$group: {
_id: null,
total: {
"$sum": {
"$switch": {
"branches": [
{
"case": {
$eq: [
"$status",
"PE"
]
},
"then": "$total"
},
{
"case": {
$eq: [
"$status",
"CO"
]
},
"then": {
$subtract: [
0,
"$total"
]
}
},
{
"case": {
$eq: [
"$status",
"CA"
]
},
"then": {
$subtract: [
0,
"$total"
]
}
}
],
default: 0
}
}
}
}
}
])
Mongo Playground
Assuming these are the only status options, one way is to $group using $cond:
db.collection.aggregate([
{$group: {
_id: 0,
total: {
$sum: {$cond: [{$eq: ["$status", "PE"]}, "$total", {$multiply: ["$total", -1]}]}
}
}}
])
See how it works on the playground example

Mongo aggregation: Count documents based on array field matching condition

I am trying to collects some statistics from a mongo collection using an aggregation pipeline but can not seem to solve my issue.
I have a collection of documents looking like this.
{
_id: “36723678126”,
dates: [ 2020-03-10T10:17:48.799Z, 2020-03-10T08:46:50.347Z, 2019-07-11T13:16:17.294Z ]
}
I would like count the number of documents that meet the following conditions
At least one date in the last 30 days
No dates between 60 and 30 days ago
At least one date before 60 days ago
Would really appreciate any help! :)
This should solve the problem (https://mongoplayground.net/p/B3LbEo8UaHA):
Test data:
[
{
_id: 1,
dates: [
ISODate("2020-03-10T10:17:48.799Z"),
ISODate("2020-03-10T08:46:50.347Z"),
ISODate("2019-07-11T13:16:17.294Z")
]
},
{
_id: 2,
dates: [
ISODate("2020-04-10T10:17:48.799Z"),
ISODate("2020-05-10T08:46:50.347Z"),
ISODate("2019-10-11T13:16:17.294Z")
]
}
]
Query:
db.collection.aggregate([
// create the new fields based on the date rules and count occurences of array
{
$addFields: {
"last_30_days": {
$sum: {
$map: {
"input": "$dates",
"as": "d",
"in": {
$cond: {
"if": {
$lte: [
"$$d",
{
$subtract: [
"$$NOW",
2592000000 // 30 days
]
}
]
},
"then": 1,
"else": 0
}
}
}
}
},
"between_30_60": {
$sum: {
$map: {
"input": "$dates",
"as": "d",
"in": {
$cond: {
"if": {
$and: [
{
$lt: [
"$$d",
{
$subtract: [
"$$NOW",
2592000000 // days
]
}
]
},
{
$gt: [
"$$d",
{
$subtract: [
"$$NOW",
5184000000 // 60 days
]
}
]
}
]
},
"then": 1,
"else": 0
}
}
}
}
},
"last_60_days": {
$sum: {
$map: {
"input": "$dates",
"as": "d",
"in": {
$cond: {
"if": {
$lte: [
"$$d",
{
$subtract: [
"$$NOW",
5184000000
]
}
]
},
"then": 1,
"else": 0
}
}
}
}
}
}
},
// find which document meet the conditions (1 for true, 0 for false)
{
$project: {
"meet_conditions": {
$cond: {
"if": {
$and: [
{
$gt: [
"$last_30_days", // at least one occurence
0
]
},
{
$eq: [
"$between_30_60", // no occurences
0
]
},
{
$gt: [
"last_60_days", // at least one occurence
0
]
}
]
},
"then": 1,
"else": 0
}
}
}
},
// count documents, summing the meet_conditions field (because they are 0 or 1)
{
$group: {
_id: null,
count: {
$sum: "$meet_conditions"
}
}
}
])
Result:
[
{
"_id": null,
"count": 1
}
]
So in the example above, there's just one documents that meet your conditions.
Obs.: To calculate the days you have to do (the bold numbers are the day you want):
1 * 1000 * 60 * 60 * 24 * 30 = 2592000000ms = 30 days
1 * 1000 * 60 * 60 * 24 * 60 = 5184000000ms = 60 days

How to write sum(if a>=10 and a<=100,a,0) like SQL in mongodb

I want to make the same SQL statement as specified select sum(if a >= 10 and a <= 100, a, 0) from table in MongoDb.
Therefore, I write:
{
"$project": {
"sumqty": {
"a1": {
"$cond": {
"if": {
"$and": [{"$gte":["$a",10]},
{"$lte":["$a",100]}]
},
"then": "$a",
"else": 0
}
}
}
}
}
Just use $cond and $group
db.table.aggregate([{
$group: {
_id: null,
totalSum: {
$sum: {
$cond: [{ $and: [{ $gte: ["$a", 10] }, { $lte: ["$a", 100] }] },
"$a",
0
]
}
}
}
}])

How to group query with multiple $cond?

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