Make the sum of booleans as integers with MongoDB - arrays

I'm trying to sum booleans (where true means 1 and false -1) in an array for each document in my collection and then sort it.
I'm using MongoDB aggregation pipeline with $addFields, $sum and $cond.
Here's the playground : https://play.db-ai.co/m/XQLKqbkkgAABTFVm
The pipeline :
[
{
"$addFields": {
"score": {
"$sum": {
"$cond": [
"$votes.value",
1,
-1
]
}
}
}
},
{
"$sort": {
"score": -1
}
}
]
The collection :
[
{
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": true
},
{
"value": false
}
]
},
{
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": false
},
{
"value": false
}
]
}
]
Actual results :
[
{
"_id": ObjectId("000000000000000000000000"),
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": true
},
{
"value": false
}
],
"score": 1
},
{
"_id": ObjectId("000000000000000000000001"),
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": false
},
{
"value": false
}
],
"score": 1
}
]
What I want :
[{
"_id": ObjectId("000000000000000000000000"),
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": true
},
{
"value": false
}
],
"score": 2
}, {
"_id": ObjectId("000000000000000000000001"),
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": false
},
{
"value": false
}
],
"score": 0
}]

I got it to work by unwinding the array and then grouping by _id again.
See Playground: https://play.db-ai.co/m/XQMFlZAtYAABLHtL
[
{
"$unwind": {
"path": "$votes"
}
},
{
"$group": {
"_id": "$_id",
"votes": {
"$push": "$votes"
},
"score": {
"$sum": {
"$cond": [
"$votes.value",
1,
-1
]
}
}
}
},
{
"$sort": {
"score": -1
}
}
]

To solve my problem I used $map multiple times. The solution of #Plancke is working but I had issues using a $match afterwards (it was always giving no results).
[
{
$addFields: {
scoresInBoolean: {
$map: {
input: '$votes',
as: 'vote',
in: '$$vote.value',
},
},
},
}, {
$addFields: {
scoresInInteger: {
$map: {
input: '$scoresInBoolean',
as: 'scoreInBoolean',
in: {
$cond: [
'$$scoreInBoolean',
1,
-1,
],
},
},
},
},
}, {
$addFields: {
score: {
$sum: '$scoresInInteger',
},
},
}
]

Related

How to use projection for nested value in mongod

How to use projection to view only the below part from all docs of the collection?
Conditions:
I need to fetch only for "type": "DEBIT" and below 2 lines, NOT all other keys in the same type.
I dont want to view other types like Account, Deposit.
{
"key": "Call",
"enabled": true,
}
Sample Docs which i have in the below structure.
{
"_id": "1",
"menu": [
{
"type": "ACCOUNT",
"scope": "ACCOUNT",
"items": [
{
"key": "Call",
"enabled": true,
},
{
"key": "Work",
"enabled": true,
}
]
},
{
"type": "DEPOSIT",
"scope": "DEPOSIT",
"items": [
{
"key": "Call",
"enabled": true,
},
{
"key": "Work",
"enabled": true,
}
]
},
{
"type": "DEBIT",
"scope": "DEBIT",
"items": [
{
"key": "Call",
"enabled": true,
},
{
"key": "Work",
"enabled": true,
}
]
},
]
}
first, you need to $unwind the menu
then $match type debit
and filter array items and then group to create the final result
db.collection.aggregate([
{
"$unwind": "$menu"
},
{
$match: {
"menu.type": "DEBIT"
}
},
{
"$project": {
_id: 1,
"menu.items": {
"$filter": {
"input": "$menu.items",
"as": "s",
"cond": {
$and: [
{
$eq: [
"$$s.enabled",
true
]
},
{
$eq: [
"$$s.key",
"Call"
]
}
]
}
}
}
}
},
{
"$group": {
"_id": "$_id",
"menu": {
"$push": "$menu"
}
}
}
])
https://mongoplayground.net/p/kRChgF9rLsI
use $filter
db.collection.aggregate([
{
"$match": {
"menu.type": "DEBIT"
}
},
{
"$set": {
"menu": {
"$filter": {
"input": "$menu",
"as": "m",
"cond": {
$eq: [
"$$m.type",
"DEBIT"
]
}
}
}
}
}
])
mongoplayground

How to reverse $unwind or re-assemble after $lookup?

I´ve been trying to reverse $unwind in nested array. Please, if you could help me it would be great. Thanks in advance.
Here are the details:
checklists collection, this collection has steps and each step has many areas, and I'd like to lookup to fill the area by id. I did it but I cannot reverse $unwind.
{
"steps": [{
"name": "paso1",
"description": "paso1",
"estimated_time": 50,
"active": true,
"areas": [{
"area_id": "60b6e728c44f0365c0d547d6"
}, {
"area_id": "60b6e7a2c44f0365c0d547d8"
}]
}, {
"name": "paso2",
"description": "o",
"estimated_time": 7,
"active": true,
"areas": [{
"area_id": "60b6e76ac44f0365c0d547d7"
}]
}, {
"name": "paso2",
"description": "l",
"estimated_time": 7,
"active": true,
"areas": [{
"area_id": "60b6e728c44f0365c0d547d6"
}]
}],
"name": "prueba",
"description": "prueba",
"type": "prueba",
"active": true,
"updated_at": {
"$date": "2021-06-02T23:56:02.232Z"
},
"created_at": {
"$date": "2021-06-01T22:44:57.114Z"
},
"__v": 0
}
area collection
{
"_id":"60b6e706c44f0365c0d547d5"
"name": "Development",
"short_name": "DEV",
"description": "Development area",
"updated_at": {
"$date": "2021-06-02T02:03:50.383Z"
},
"created_at": {
"$date": "2021-06-02T02:03:50.383Z"
},
"__v": 0,
"active": true
}
My aggregation
db.checklists.aggregate([
{
"$unwind": "$steps"
},
{
"$unwind": "$steps.areas"
},
{
"$lookup": {
"from": "areas",
"let": {
"area_id": {
"$toObjectId": "$steps.areas.area_id"
}
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": [
"$_id",
"$$area_id"
]
}
}
}
],
"as": "convertedItems"
}
},
{
"$group": {
"_id": "$steps.name",
"root": {
"$first": "$$ROOT"
},
"items": {
"$push": {
"$mergeObjects": [
"$steps.areas",
{
"$arrayElemAt": [
"$convertedItems",
0
]
}
]
}
},
}
},
{
"$addFields": {
"values": {
"$reduce": {
"input": "$items",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
{
"$cond": [
{
"$in": [
"$$this.area_id",
"$$value.area_id"
]
},
[],
[
"$$this"
]
]
}
]
}
}
}
}
},
{
"$addFields": {
"root.steps.areas": "$values"
}
},
{
"$replaceRoot": {
"newRoot": "$root"
}
},
{
"$group": {
"_id": "$_id",
"root": {
"$first": "$$ROOT"
},
"steps": {
"$push": "$steps"
}
}
},
{
"$addFields": {
"root.steps": "$steps"
}
},
{
"$replaceRoot": {
"newRoot": "$root"
}
},
{
"$project": {
"convertedItems": 0
}
}
])
I don´t get to form this output:
{
"steps": [{
"name": "paso1",
"description": "paso1",
"estimated_time": 50,
"active": true,
"areas": [{
"_id": "60b6e728c44f0365c0d547d6",
"name":"Development",
..... //join or lookup
}, {
"_id": "60b6e7a2c44f0365c0d547d8",
"name":"Development",
..... //join or lookup
}]
}],
"name": "prueba",
"description": "prueba",
"type": "prueba",
"active": true,
"updated_at": {
"$date": "2021-06-02T23:56:02.232Z"
},
"created_at": {
"$date": "2021-06-01T22:44:57.114Z"
},
"__v": 0
}
Thank you very much!
$unwind deconstruct steps array
$lookup with areas collection pass area_id in let
$match to check is _id in area_ids after converting to string
$project to show required fields
$group by _id and reconstruct the steps array and pass your required fields
db.checklists.aggregate([
{ $unwind: "$steps" },
{
$lookup: {
from: "areas",
let: { area_id: "$steps.areas.area_id" },
pipeline: [
{
$match: {
$expr: { $in: [{ $toString: "$_id" }, "$$area_id"] }
}
},
{ $project: { name: 1 } }
],
as: "steps.areas"
}
},
{
$group: {
_id: "$_id",
steps: { $push: "$steps" },
name: { $first: "$name" },
description: { $first: "$description" },
type: { $first: "$type" },
active: { $first: "$active" },
updated_at: { $first: "$updated_at" },
created_at: { $first: "$created_at" },
__v: { $first: "$__v" }
}
}
])
Playground

Mongo DB $look up Method for Fields in Arrays instead of Collections

I have a user document with the following structure:
{
"_id": {
"$oid": "5e636c552b872f00178033bf"
},
"finance": {
"expenditure": [
{
"status": true,
"_id": {
"$oid": "5e636d442b872f00178033d4"
},
"amount": {
"$numberInt": "900"
},
"category": "Coffee"
},
{
"status": true,
"_id": {
"$oid": "5e636d492b872f00178033d5"
},
"amount": {
"$numberInt": "1000"
},
"category": "Coffee"
},
{
"status": true,
"_id": {
"$oid": "5e636d532b872f00178033d6"
},
"amount": {
"$numberInt": "3000"
},
"category": "Sport"
},
{
"status": true,
"_id": {
"$oid": "5e636d572b872f00178033d7"
},
"amount": {
"$numberInt": "1000"
},
"category": "Sport"
},
],
"customcategories": [
{
"budget": {
"$numberInt": "200"
},
"_id": {
"$oid": "5e636c552b872f00178033c7"
},
"title": "Sport"
},
{
"budget": {
"$numberInt": "100"
},
"_id": {
"$oid": "5e636c552b872f00178033c8"
},
"title": "Coffee"
}
]
}
}
My previos command is this one (you don't have to mind the status and the currentdate) :
User.aggregate([
{
$match: {
_id: req.user._id
}
},
{
$unwind: "$finance.expenditure"
},
{
$match: {
"finance.expenditure.status": true
}
},
{
$sort: {
"finance.expenditure.currentdate": -1
}
},
{
$group: {
_id: "$finance.expenditure.category",
amount: {
$sum: "$finance.expenditure.amount",
}
}
},
{
$project: {
_id: 0,
category: "$_id",
amount: 1
}
}
])
The Result looks like this :
{
"expenditure": [
{
"amount": 1900,
"category": "Coffee"
},
{
"amount": 4000,
"category": "Sport"
}
]
}
I would like to add the my grouped elements the budget from the associated "customcategory".
So that it looks like this :
{
"expenditure": [
{
"amount": 1900,
"category": "Coffee",
"budget" : 100
},
{
"amount": 4000,
"category": "Sport",
"budget" : 200
}
]
}
I tried several things but nothing works of the $lookup method worked for me.
I hope some can help me :)
give this pipeline a try:
db.collection.aggregate([
{
$match: { _id: ObjectId("5e636c552b872f00178033bf") }
},
{
$unwind: "$finance.expenditure"
},
{
$match: { "finance.expenditure.status": true }
},
{
$sort: { "finance.expenditure.currentdate": -1 }
},
{
$group: {
_id: "$finance.expenditure.category",
amount: { $sum: "$finance.expenditure.amount"},
categories: { $first: '$finance.customcategories' }
}
},
{
$project: {
_id: 0,
category: "$_id",
amount: 1,
budget: {
$arrayElemAt: [
{
$map: {
input: {
$filter: {
input: '$categories',
cond: { $eq: ['$$this.title', '$_id'] }
}
},
in: '$$this.budget'
}
},
0
]
}
}
}
])
https://mongoplayground.net/p/adsWInz3wgY
Try this one:
User.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId(req.user._id)
}
},
{
$sort: {
"finance.expenditure.currentdate": -1
}
},
{
$unwind: "$finance.expenditure"
},
{
$unwind: "$finance.customcategories"
},
{
$match: {
"finance.expenditure.status": true
}
},
{
$group: {
_id: "$finance.expenditure.category",
amount: {
$addToSet: "$finance.expenditure"
},
customcategories: {
$addToSet: "$finance.customcategories"
}
}
},
{
$project: {
_id: 0,
"amount": {
$sum: "$amount.amount"
},
"category": "$_id",
"budget": {
$sum: {
$let: {
vars: {
budget: {
$filter: {
input: "$customcategories",
cond: {
$eq: [
"$_id",
"$$this.title"
]
}
}
}
},
in: "$$budget.budget"
}
}
}
}
}
])
//.exec(function(err, result){})
MongoPlayground

How to unwind array inside object in MongoDB?

I have about MongoDB in unwind operator.
So, I have document like this.
{
"name": "abc",
"report": {
"_2019": {
"May": {
"_9": {
"DATA": [{
"image": "xyz.png",
"object": true
},
{
"image": "abc.png",
"object": true
}
]
},
"_10": {
"DATA": [{
"image": "ejf.png",
"object": false
},
{
"image": "qwe.png",
"object": false
}
]
}
},
"June": {
"_1": {
"DATA": [{
"image": "jsk.png",
"object": false
}]
}
}
},
"_2020": {
"January": {
"_30": {
"DATA": [{
"image": "hhg.png",
"object": false
}]
}
}
}
}
}
And want to format the output for something like this
[{
"image": "xyz.png",
"object": true
}, {
"image": "abc.png",
"object": true
}, {
"image": "ejf.png",
"object": false
}, {
"image": "qwe.png",
"object": false
}, {
"image": "jsk.png",
"object": false
}, {
"image": "hhg.png",
"object": false
}]
The first thing i found is that using unwind, but it only accept array. And the second is using foreach in the programming-side. But i think it's not effective. Is this possible? Thank you.
The problem here is that the keys like _2020 or January or _30 are dynamically generated. To access subdocuments from DATA level you need to get there by using $objectToArray and $map to take values from key-value pairs. After each of these steps you need $unwind and then in the last step you can run $replaceRoot to promote documents from DATA into root level:
db.col.aggregate([
{
$project: {
data: {
$map: {
input: { $objectToArray: "$report" },
in: "$$this.v"
}
}
}
},
{ $unwind: "$data" },
{
$project: {
data: {
$map: {
input: { $objectToArray: "$data" },
in: "$$this.v"
}
}
}
},
{ $unwind: "$data" },
{
$project: {
data: {
$map: {
input: { $objectToArray: "$data" },
in: "$$this.v"
}
}
}
},
{ $unwind: "$data" },
{ $unwind: "$data.DATA" },
{
$replaceRoot: {
newRoot: "$data.DATA"
}
}
])
Mongo Playground

MongoDB: How to filter true / false result returning from $ne/ $eq operator in a single query?

Here Possible duplicaion but not much useful.
I have a collection like this
{
"_id": {
"$oid": "589764fb40948e196cc90e8a"
},
"color": "red",
"tweets": ["I am fine", "I am ok"],
"userId": "172884537",
"tweetIds": ["819223623735119873", "819219362049572864"]
} {
"_id": {
"$oid": "589764fb40948e196cc90e8b"
},
"color": "red",
"tweets": ["How are you?", "Where are you"],
"userId": "4558206579",
"tweetIds": ["822916538596462592"]
} {
"_id": {
"$oid": "589764fb40948e196cc90e8c"
},
"color": "blue",
"tweets": ["Whats up?", "Good night"],
"userId": "1893540588",
"tweetIds": ["822947258186403840", "822498809808728064"]
} {
"_id": {
"$oid": "589764fb40948e196cc90e8d"
},
"color": "red",
"tweets": ["trump"],
"userId": "781950015858176001",
"tweetIds": ["819486328467374081", "819220448282079233"]
}
I want to get those userId where the number of tweets and the number of tweetsIds are not equal.
I tried in two way
db.us_election_nodes_with_tweets.aggregate([{
"$project": {
"_id": 1,
"alloc": {
"$ne": [{
"$size": "$tweets"
}, {
"$size": "$tweetIds"
}]
}
}
}, {
"$match": {
"alloc": 1
}
}])
And the other
db.us_election_nodes_with_tweet.find({
$and: [{
result: {
"$ne": [{
$size: "$tweets"
}, {
$size: "$tweetIds"
}]
}
}, {
result: {
$exists: true
}
}]
}).pretty()
If I do this
db.us_election_nodes_with_tweet.aggregate([{
$project: {
_id: 0,
userId: 1,
result: {
"$ne": [{
$size: "$tweets"
}, {
$size: "$tweetIds"
}]
}
}
}])
I get an output like this. Because $ne returns true it doesn't match and return false where it's matches.
{ "userId" : "172884537", "result" : false }
{ "userId" : "781950015858176001", "result" : true}
{ "userId" : "4558206579", "result" : true }
{ "userId" : "1893540588", "result" : false }
But here I don't know how to filter only Boolean true from this result. Do you have any suggestions?
Change your $match to check for true.
db.us_election_nodes_with_tweets.aggregate([ { "$project": { "_id": 1, "userId":1, "alloc": { "$ne": [ { "$size": "$tweets" }, { "$size": "$tweetIds" } ] } }}, { "$match": { "alloc": true } } ])
As an alternative, you can use $redact which will $$PRUNE when the array size matches else $$KEEP the row .
db.us_election_nodes_with_tweet.aggregate([{
"$redact": {
"$cond": [{
"$eq": [ { "$size": "$tweets" }, { "$size": "$tweetIds" } ]
},
"$$PRUNE",
"$$KEEP"
]
}
}])

Resources