Match two equal fields of arrays of same documents without $unwind - node.js

I have below workouts collection
[
{
"receiverWorkout": {
"exercises": [
{ "_id": "1", "reps": 0 },
{ "_id": "2", "reps": 4 }
]
},
"senderWorkout": {
"exercises": [
{ "_id": "2", "reps": 12 },
{ "_id": "1", "reps": 8 }
]
}
}
]
I need to match same _id from receiverWorkout and senderWorkout exercises and want to add a key won true or false whose reps are greater.
If you can see first element of receiverWorkout exercises({ "_id": "1", "reps": 0 }) is matching with the second element of the senderWorkout exercises({ "_id": "1", "reps": 8 }) because both have same _id
Expected output should be something like
[
{
"receiverWorkout": {
"exercises": [
{ "_id": "1", "reps": 0, "won": false },
{ "_id": "2", "reps": 4, "won": false }
]
},
"senderWorkout": {
"exercises": [
{ "_id": "2", "reps": 12, "won": true },
{ "_id": "1", "reps": 8, "won": true }
]
}
}
]
I want to avoid $unwind
Please help!!!

You can use below aggregation.
Single Pass
db.colname.aggregate([
{"$project":{
"exercises":{
"$reduce":{
"input":"$receiverWorkout.exercises",
"initialValue":{"receiverWorkout":[],"senderWorkout":[]},
"in":{"$let":{
"vars":{
"sen":{"$arrayElemAt":["$senderWorkout.exercises",{"$indexOfArray":["$senderWorkout.exercises._id","$$this._id"]}]}
},
"in":{
"receiverWorkout":{
"$concatArrays":[
"$$value.receiverWorkout",
[{"_id":"$$this._id","reps":"$$this.reps","won":{"$gt":["$$this.reps","$$sen.reps"]}}]
]
},
"senderWorkout":{
"$concatArrays":[
"$$value.senderWorkout",
[{"_id":"$$sen._id","reps":"$$sen.reps","won":{"$gt":["$$sen.reps","$$this.reps"]}}]
]
}
}
}}
}
}
}},
{"$project":{
"receiverWorkout.exercises":"$exercises.receiverWorkout",
"senderWorkout.exercises":"$exercises.senderWorkout"
}}
])
Without using $unwind
db.colname.aggregate(
{"$project":{
"receiverWorkout.exercises":{
"$map":{
"input":"$receiverWorkout.exercises",
"in":{
"$let":{
"vars":{"sen":{"$arrayElemAt":["$senderWorkout.exercises",{"$indexOfArray":["$senderWorkout.exercises._id","$$this._id"]}]}},
"in":{
"_id":"$$this._id",
"reps":"$$this.reps",
"won":{"$gt":["$$this.reps","$$sen.reps"]}
}
}
}
}
},
"senderWorkout.exercises":{
"$map":{
"input":"$senderWorkout.exercises",
"in":{
"$let":{
"vars":{"rec":{"$arrayElemAt":["$receiverWorkout.exercises",{"$indexOfArray":["$receiverWorkout.exercises._id","$$this._id"]}]}},
"in":{
"_id":"$$this._id",
"reps":"$$this.reps",
"won":{"$gt":["$$this.reps","$$rec.reps"]}
}
}
}
}
}
}})
With $unwind
db.colname.aggregate([
{"$unwind":"$senderWorkout.exercises"},
{"$unwind":"$receiverWorkout.exercises"},
{"$sort":{"receiverWorkout.exercises._id":1,"senderWorkout.exercises._id":1}},
{"$addFields":{
"receiverWorkout.exercises.won":{
"$cond":[
{"$gt":["$receiverWorkout.exercises.reps","$senderWorkout.exercises.reps"]},
true,
false
]
},
"senderWorkout.exercises.won":{
"$cond":[
{"$gt":["$receiverWorkout.exercises.reps","$senderWorkout.exercises.reps"]},
false,
true
]
}
}},
{"$group":{
"_id":"$_id",
"receiverWorkout":{"$addToSet":"$receiverWorkout.exercises"},
"senderWorkout":{"$addToSet":"$senderWorkout.exercises"}
}}
])

You don't need to use array index to achieve this. If I good understand, you need to know, for each entry in your senderWorkout.exercises array, if an element with the same _id is present in receiverWorkout.exercises array. Here's the way to do this :
db.collection.aggregate([
{
$addFields: {
"senderWorkout.exercises": {
$map: {
input: "$senderWorkout.exercises",
as: "ex",
in: {
$cond: {
if: {
$in: [
"$$ex._id",
"$receiverWorkout.exercises._id"
]
},
then: {
_id: "$$ex._id",
reps: "$$ex.reps",
result: true
},
else: {
_id: "$$ex._id",
reps: "$$ex.reps",
result: false
}
}
}
}
}
}
}
])
you can try it here

Related

MongoDB aggregation group by ID then code

I have this collection:
[{
"_id": "5c378eecd11e570240a9b0ac",
"userID": "1",
"isActive": "Active",
"areaCode": "A-1",
"__v": 0
},
{
"_id": "5c378eecd11e570240a9b0bb",
"userID": "1",
"isActive": "Active",
"areaCode": "A-2",
"__v": 0
},
{
"_id": "5c378eecd11e570240a9b0c5",
"userID": "2",
"isActive": "Active",
"areaCode": "A-1",
"__v": 0
}]
Need help in grouping the results by user ID then the area code using aggregation but I'm not getting the desired output. Here's what I've tried:
AreaCodes.aggregate([
{
'$match': { '$and': [
{ 'isActive': 'Active' },
{ 'userID': { '$exists': true } }
]
}
},
{
'$group': {
'_id': {
'userID': '$userID'
},
'entries': {
'$push': {
'areaCode': '$areaCode'
}
}
}
},
{
'$group': {
'_id': '$_id.userID',
'areaCodes': {
'$push': {
'areaCode': '$entries'
}
}
}
},
{
'$project': {
'_id': 0,
'userID': '$_id',
'areaCodes': '$areaCodes'
}
}
])
Which returns the following:
[
{
"userID": "1",
"areaCodes": [
{
"areaCode": [
{
"areaCode": "A-1"
},
{
"areaCode": "A-2"
}
]
}
]
},
{
"userID": "2",
"areaCodes": [
{
"areaCode": [
{
"areaCode": "A-1"
}
]
}
]
}
]
My desired output would be to remove the excess areaCode objects and group them inside an array for each user like:
[
{
"userID": "1",
"areaCodes": ["A-1", "A-2"]
},
{
"userID": "2",
"areaCodes": ["A-1"]
}
]
How to achieve this format? Thanks.
How about:
db.collection.aggregate([
{
$match: {
$and: [{ "isActive": "Active" }, {"userID": {"$exists": true}}]
}
},
{
$group: {
_id: '$userID',
areaCodes: {$addToSet: "$areaCode"}
}
},
{
$project: {
_id: 0,
userID: "$_id",
areaCodes: 1
}
}
])
As you can see on this playgeound example.
If you just want the matching areaCodes, you can simply use $addToSet.

MongoDB update a document field in Array of Arrays

How to update ($set) a field "text" that matching "callback_data":"like" in document like this:
"data": {
"buttons": [
[{
"text": "Button",
"url": "https://example.org"
}],
[{
"text": "👍",
"callback_data": "like"
}, {
"text": "👎",
"callback_data": "dislike"
}]
]
}
Demo - https://mongoplayground.net/p/_g9YmDY5WMn
Use - update-documents-with-aggregation-pipeline
$map $mergeObjects $cond
db.collection.update({},
[
{
$set: {
"data.buttons": {
$map: {
input: "$data.buttons",
in: {
$map: {
input: "$$this", // array inside buttons
in: {
$cond: [
{ $eq: [ "$$this.callback_data", "like" ] }, // condition
{ $mergeObjects: [ "$$this", { text: "changed" } ] }, // true
"$$this" // false
]
}
}
}
}
}
}
}
],
{
multi: true
})

Group by the content of array of string with out order in mongo aggregation

i have a problem with aggregation framework in MongoDB (mongoose) this is the problem. i have the following database scheme.so what i want to do is count number of people who has access through Mobile only , Card only, or both. with out any order,
{
'_id': ObjectId,
'user_access_type': ['Mobile' , 'Card']
}
{
'_id': ObjectId,
'user_access_type': ['Card' , 'Mobile']
}
{
'_id': ObjectId,
'user_access_type': ['Mobile']
}
{
'_id': ObjectId,
'user_access_type': ['Card']
}
Now i am using this but it only groups by the order of the user_access_type array,
[ { "$group" : { "_id": {"User" : "$user_access_type"} , "count": {"$sum" : 1} }]
this is the output:
{
"_id": {
"User": [
"Card",
"Mobile"
]
},
"count": 1
},
{
"_id": {
"_id": "5f7dce2359aaf004985f98eb",
"User": [
"Mobile",
"Card"
]
},
"count": 1
},
{
"_id": {
"User": [
"Mobile"
]
},
"count": 1
},
{
"_id": {
"User": [
"Card"
]
},
"count": 1
},
vs what i want:
{
"_id": {
"User": [
"Card",
"Mobile" // we can say both
]
},
"count": 2 // does not depend on order
},
{
"_id": {
"User": [
"Mobile"
]
},
"count": 1
},
{
"_id": {
"User": [
"Card"
]
},
"count": 1
},
You can use other option as well using $function,
$function can allow to add javascript code, you can use sort() to sort the array
db.collection.aggregate([
{
$addFields: {
user_access_type: {
$function: {
body: function(user_access_type){
return user_access_type.sort();
},
args: ["$user_access_type"],
lang: "js"
}
}
}
},
{
$group: {
_id: "$user_access_type",
count: { $sum: 1 }
}
}
])
Second option,
If user_access_type array having always unique elements then you can use $setUnion operator on user_access_type array as self union, some how this will re-order array in same order,
db.collection.aggregate([
{
$addFields: {
user_access_type: {
$setUnion: "$user_access_type"
}
}
},
{
$group: {
_id: "$user_access_type",
count: { $sum: 1 }
}
}
])
Playground

how get count from mongodb with different status from one collection

I have appointment collection in that i have status codes like upcoming, cancelled, completed. i want to write an api to get count of each status using mongoose or mongodb methods.
output should be like below
[{
group : "grp1",
appointments_completed :4
appointments_upcoming :5
appointments_cancelled : 7
}]
thanks in advance.
I hope it help you
db.getCollection('codelist').aggregate([
{
$group:{
_id:{status:"$status"},
count:{$sum:1}
}
}
])
The result will be
[{
"_id" : {
"status" : "cancelled"
},
"count" : 13.0
},
{
"_id" : {
"status" : "completed"
},
"count" : 20.0
}
]
I think you can process it with nodejs
Using Aggregation Pipeline $group we can get this count
db.collection_name.aggregate([
{ $group: {
_id:null,
appointments_completed: {$sum : "$appointments_completed" },
appointments_upcoming:{$sum :"$appointments_upcoming"},
appointments_cancelled:{$sum: "$appointments_cancelled"}
}
}
]);
With MongoDb 3.6 and newer, you can leverage the use of $arrayToObject operator and a $replaceRoot pipeline to get the desired result. You would need to run the following aggregate pipeline:
db.appointments.aggregate([
{ "$group": {
"_id": {
"group": <group_by_field>,
"status": { "$concat": ["appointments_", { "$toLower": "$status" }] }
},
"count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.group",
"counts": {
"$push": {
"k": "$_id.status",
"v": "$count"
}
}
} },
{ "$addFields": {
"counts": {
"$setUnion": [
"$counts", [
{
"k": "group",
"v": "$_id"
}
]
]
}
} },
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$counts" }
} }
])
For older versions, a more generic approach though with a different output format would be to group twice and get the counts as an array of key value objects as in the following:
db.appointments.aggregate([
{ "$group": {
"_id": {
"group": <group_by_field>,
"status": { "$toLower": "$status" }
},
"count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.group",
"counts": {
"$push": {
"status": "$_id.status",
"count": "$count"
}
}
} }
])
which spits out:
{
"_id": "grp1"
"counts":[
{ "status": "completed", "count": 4 },
{ "status": "upcoming", "count": 5 }
{ "status": "cancelled", "count": 7 }
]
}
If the status codes are fixed then the $cond operator in the $group pipeline step can be used effectively to evaluate the counts based on the status field value. Your overall aggregation pipeline can be constructed as follows to produce the result in the desired format:
db.appointments.aggregate([
{ "$group": {
"_id": <group_by_field>,
"appointments_completed": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "completed" ] }, 1, 0 ]
}
},
"appointments_upcoming": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "upcoming" ] }, 1, 0 ]
}
},
"appointments_cancelled": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "cancelled" ] }, 1, 0 ]
}
}
} }
])

$eq not working with $unwind result mongoose

i've simple schema where Events.like store objectId of user. i'd like to match a particular user in array (Events.like) and add a flag isLiked according to match.
Events.aggregate([
{ $unwind: '$like'},
{
"$project":{
// "count" : {$size: "$like"},
"like":1,
"isLiked" :{ "$eq": [ "like", "593564b94642650d5b09f16b" ] },
// "isLiked" : { "$cond": [{ "$eq": [ "like", "593564b94642650d5b09f16b" ] }, true, false ] } }
}
}
], function(err, list) { }
this method always give me false. see results
"data": [
{
"_id": "593647ae9e10082982d3f7a2",
"like": "593563f66d2e9f0b84553fc3",
"isLiked": false
},
{
"_id": "593643a5a1e73a2841d3cddb",
"like": "593564b94642650d5b09f16b",
"isLiked": false
},
{
"_id": "593643a5a1e73a2841d3cddb",
"like": "593563f66d2e9f0b84553fc3",
"isLiked": false
}
]
can anyone tell me where i'm mistaking
########## update ##########
{ $unwind: '$like'},
{
"$project":{
"isLiked" :{ "$eq": [ "$like", mongoose.Types.ObjectId("593564b94642650d5b09f16b") ] },
}
},
{
$group: {
_id: '$_id',
'likes': { $sum: 1},
"like": { $push: '$like' },
"isLiked" : 1
}
}
then
MongoError: the group aggregate field 'isLiked' must be defined as an expression inside an object
i simple want below result
"data": [
{
"_id": "593647ae9e10082982d3f7a2",
"like": ["593563f66d2e9f0b84553fc3","593643a5a1e73a2841d3cddb" ],
"likes":2
"isLiked": true
},
{
"_id": "593643a5a1e73a2841d3cddb",
"like": "[593563f66d2e9f0b84553fc3"],
"likes":1,
"isLiked": false
}
]
Hi the user id you are passing is being passed as a string you will need to convert it to objectId. Try this
EDIT: fixed path of the field as pointed by the asker, #ShivShanker
Events.aggregate([
{ $unwind: '$like'},
{
"$project":{
// "count" : {$size: "$like"},
"like":1,
"isLiked" :{ "$eq": [ "$like", mongoose.Types.ObjectId("593564b94642650d5b09f16b") ] }
}
}
], function(err, list) { }

Resources