add key to nested array with condition - node.js

I have a simple datastructure in mongodb:
{
_id: ObjectID,
name: 'Name',
birthday: '25.05.2001'
items: [
{
_id: ObjectID,
name: 'ItemName',
info: 'ItemInfo',
},
{
_id: ObjectID,
name: 'ItemName',
info: 'ItemInfo',
}
]
}
Now i want a query, that takes a ObjectID (_id) of an item as criteria and gives me back the object with all items in the array AND projects a new field "selected" with value true or false into a field in the result of each array item:
I tried that with this query:
{ $unwind: '$items' },
{
$project: {
selected: {
$cond: { if: { 'items._id': itemObjectID }, then: true, else: false },
},
},
},
but MongoDB gives me back an error:
MongoError: FieldPath field names may not contain '.'.
Have no clue why its not working, any help or ideas? Thank you very much!

What you are missing here is $eq aggregation operator which checks the condition for the equality.
You can try below aggregation here if you want to check for ObjectId then you need to put mongoose.Types.ObjectId(_id)
db.collection.aggregate([
{ "$unwind": "$items" },
{ "$addFields": {
"items.selected": {
"$eq": [
1111,
"$items._id"
]
}
}},
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"items": {
"$push": {
"_id": "$items._id",
"selected": "$items.selected"
}
}
}}
])
Will give following output
[
{
"_id": ObjectId("5a934e000102030405000000"),
"items": [
{
"_id": 1111,
"selected": true
},
{
"_id": 2222,
"selected": false
}
],
"name": "Name"
}
]
You can check it here

#Ashish: Thank you very much for your help! Your answer helped me to build the right query for me:
db.collection.aggregate([
{
$unwind: "$items"
},
{
$project: {
"items.name": 0,
"birthday": 0
}
},
{
"$addFields": {
"items.selected": {
"$eq": [
1111,
"$items._id"
]
}
}
},
{
$group: {
_id: "$_id",
"name": {
"$first": "$name"
},
items: {
$push: "$items"
}
}
},
{
$match: {
"items._id": {
$eq: 1111
}
}
},
])
and leads to a result that looks like:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"items": [
{
"_id": 1111,
"selected": true
},
{
"_id": 2222,
"selected": false
}
],
"name": "Name"
}
]

Related

Percentage of amount in a subdocument grouped per type in Mongoose/NodeJS

I have the following MongoDB schema:
const userSchema = new mongoose.Schema({
email: {
type: String,
required: [true, 'Email is required.']
},
transactions: [
{
categoryName: {
type: String,
required: [true, 'Category name in transaction is required.']
},
categoryType: {
type: String,
required: [true, 'Category type in transaction is required.']
},
amount: {
type: Number,
required: [true, 'Transaction amount is required.']
}
}
]})
transactions.categoryType can only be Income or Expense. Now per queried _id, I want to return the ratio/percentage of transactions.CategoryName per Income and Expense. Meaning if I have the following data:
{
"_id": 000001,
"email": "asdasd#email.com"
"transactions": [
{
"categoryName": "Food",
"categoryType": "Expense",
"amount": 200
},
{
"categoryName": "Rent",
"categoryType": "Expense",
"amount": 1000
},
{
"categoryName": "Salary",
"categoryType": "Income",
"amount": 15000
}
]
}
the result that I would want is:
{ "email": "asdasd#email.com",
"Income": [["Salary", 100]],
"Expense": [["Food", 16.67],["Rent",83.33]],
}
Now, I have the following query:
return User.aggregate([
{ $match: { _id : ObjectId(request.params.id) } },
{ $unwind : "$transactions"},
{ $group : { _id : { type: "$transactions.categoryType" },
        total: {$sum : "$transactions.amount"},
transactionsArray: { $push: "$transactions"}
        }
},
{ $project: {
_id: 0,
transactionsArray:1,
    type: "$_id.type",
total:1
}
}
])
which returns a data like this:
[
{
"total": 1200,
"transactions": [
{
"categoryName": "Food",
"categoryType": "Expense",
"amount": 200,
},
{
"categoryName": "Rent",
"categoryType": "Expense",
"amount": 1000,
}
],
"type": "Expense"
},
{
"total": 15000,
"transactions": [
{
"categoryName": "Salary",
"categoryType": "Income",
"amount": 15000,
}
],
"type": "Income"
}
]
Now, I do not know how am I going to further process the result set to divide the transactions.amount by the total to get the result that I want.
You may go with multiple steps in aggregations
$unwind to deconstruct the array
$group- first group to group by _id and $categoryType. So we can get the total amount and an amount for particular transaction. This helps to calculate the ratio.
$map helps to loop over the array and calculate the ratio
$reduce- You need comma separated string array of objects. So loop it and get the structure.
$group to group by _id only so we can get the key value pair of category type and Income/Expense when we push
$replaceRoot to make the $grp object as root which should be merged with already existing fields ($mergeObjects)
$project for remove unwanted fields
Here is the code
db.collection.aggregate([
{ "$unwind": "$transactions" },
{
"$group": {
"_id": { id: "$_id", catType: "$transactions.categoryType" },
"email": { "$first": "$email" },
"amount": { "$sum": "$transactions.amount" },
"category": {
$push: { k: "$transactions.categoryName", v: "$transactions.amount" }
}
}
},
{
$addFields: {
category: {
$map: {
input: "$category",
in: {
k: "$$this.k",
v: {
"$multiply": [
{ "$divide": [ "$$this.v","$amount" ]},
100
]
}
}
}
}
}
},
{
"$addFields": {
category: {
"$reduce": {
"input": "$category",
"initialValue": [],
"in": {
"$concatArrays": [
[
[ "$$this.k", { $toString: "$$this.v" } ]
],
"$$value"
]
}
}
}
}
},
{
"$group": {
"_id": "$_id.id",
"email": { "$first": "$email" },
"grp": { "$push": { k: "$_id.catType", v: "$category" } }
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [ { "$arrayToObject": "$grp" }, "$$ROOT" ]
}
}
},
{ "$project": { grp: 0 } }
])
Working Mongo playground

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

MongoDB aggregation : Group by Category and sum up the amount

I have the following structure in my collection (you don't have to mind the status) :
{
"_id": {
"$oid": "5e6355e71b14ee00175698cb"
},
"finance": {
"expenditure": [
{
"status": true,
"_id": { "$oid": "5e63562d1b14ee00175698df" },
"amount": { "$numberInt": "100" },
"category": "Sport"
},
{
"status": true,
"_id": { "$oid": "5e6356491b14ee00175698e0" },
"amount": { "$numberInt": "200" },
"category": "Sport"
},
{
"status": true,
"_id": { "$oid": "5e63565b1b14ee00175698e1" },
"amount": { "$numberInt": "50" },
"category": "Outdoor"
},
{
"status": true,
"_id": { "$oid": "5e63566d1b14ee00175698e2" },
"amount": { "$numberInt": "400" },
"category": "Outdoor"
}
]
}
}
My previos command was this:
User.aggregate([
{ $match: {_id: req.user._id} },
{ $unwind: '$finance.expenditure' },
{ $match: {'finance.expenditure.status': true} },
{ $sort: {'finance.expenditure.currentdate': -1} },
{
$group: {
_id: '$_id',
expenditure: { $push: '$finance.expenditure' }
}
}
])
With this I just get every single expenditure back.
But now I want to group the expenditures by their category and sum up the amount of every single expenditure for their group.
So it should look like this:
{ "amount": 300 }, "category": "Sport" },
{ "amount": 450 }, "category": "Outdoor" }
Thanks for your help
Instead of grouping on _id field group on category field & sum amount field:
db.collection.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
}
}
])
Test : MongoDB-Playground

MongoDB Aggregate Pipeline query

I'm using mongoDB 3.6 on node.js 8.11.1 and working with MongoDB Node.js Driver.
I have two collections, 'group' and 'user':
group:
[
{
"_id":1,
"groupName":"group1",
"users":[
{
"userId":1,
"isAdmin":"false"
},
{
"userId":2,
"isAdmin":"true"
}
]
},
{
"_id":2,
"groupName":"group2",
"users":[
{
"userId":2,
"isAdmin":"false"
},
{
"userId":3,
"isAdmin":"true"
}
]
}
]
user:
[
{
"_id":1,
"username":"user1",
"firstname":"a",
"lastname":"aa",
"mobileNo":"+1111111"
},
{
"_id":2,
"username":"user2",
"firstname":"b",
"lastname":"bb",
"mobileNo":"+2222222"
},
{
"_id":3,
"username":"user3",
"firstname":"c",
"lastname":"cc",
"mobileNo":"+3333333"
}
]
I need an aggregate to return something like this:
[
{
"_id":1,
"groupName":"group1",
"members":[
{
"isAdmin":"false",
"username":"user1",
"firstname":"a",
"lastname":"aa"
},
{
"isAdmin":"true",
"username":"user2",
"firstname":"b",
"lastname":"bb"
}
]
},
{
"_id":2,
"groupName":"group2",
"members":[
{
"isAdmin":"false",
"username":"user2",
"firstname":"b",
"lastname":"bb"
},
{
"isAdmin":"true",
"username":"user3",
"firstname":"c",
"lastname":"cc"
}
]
}
]
At "members" in result, "isAdmin" return from "users" at group collection and "username", "firstname" and "lastname" came from user collection
Many thanks,
Milad.
You can try below aggregation from mongodb 3.6 and above
db.group.aggregate([
{ "$unwind": "$users" },
{ "$lookup": {
"from": Users.collection.name,
"let": { "userId": "$users.userId", "isAdmin": "$users.isAdmin" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$userId" ] } } },
{ "$project": { "isAdmin": "$$isAdmin", "username": 1, "firstName": 1, "lastName": 1 }}
],
"as": "members"
}},
{ "$unwind": "$members" },
{ "$group": {
"_id": "$_id",
"members": { "$push": "$members" },
"groupName": { "$first": "$groupName" }
}}
])

$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