Compare and add two array inside nested arrays - node.js

I have below array of array inside my document
{
items: [
["Tax", 10, 20, 30],
["FX Adjustments", 10, 20, 30],
["Tax", 10, 20, 30],
["FX Adjustments", 10, 20, 30]
]
}
I need to combine Tax with Tax and FX Adjustments with FX Adjustments.
Output I need
{
items: [
["Tax", 20, 40, 60],
["FX Adjustments", 20, 40, 60]
]
}
I tried but with no luck
db.collection.aggregate([
{
$project: {
items: {
$reduce: {
input: "$items",
initialValue: [],
in: {
$cond: [
]
}
}
}
}
}
])
Kindly help with this
Thank you!!!

You need to start with $unwind and then $group by first array element ($arrayElemAt). Then you can run $reduce like you tried but since your array is multidimensional, you need to wrap it with $map to represent the index. To get back the original data format you can group by null and use $concatArrays to put grouping _id as first array element:
db.collection.aggregate([
{
$unwind: "$items"
},
{
$group: {
_id: { $arrayElemAt: [ "$items", 0 ] },
values: { $push: { $slice: [ "$items", 1, { $size: "$items" } ] } }
}
},
{
$project: {
_id: 1,
values: {
$map: {
input: { $range: [ 0, { $size: { $arrayElemAt: [ "$values", 0 ] } } ] },
as: "index",
in: {
$reduce: {
input: "$values",
initialValue: 0,
in: { $add: [ "$$value", { $arrayElemAt: [ "$$this", "$$index" ] } ] }
}
}
}
}
}
},
{
$group: {
_id: null,
items: { $push: { $concatArrays: [ [ "$_id" ], "$values" ] } }
}
}
])
Mongo Playground

Try below aggregate pipeline
collectionName.aggregate([
{
$unwind: "$items"
},
{
$group: {
_id: {
$arrayElemAt: [
"$items",
0
]
},
sum1: {
$sum: {
$arrayElemAt: [
"$items",
1
]
}
},
sum2: {
$sum: {
$arrayElemAt: [
"$items",
2
]
}
},
sum3: {
$sum: {
$arrayElemAt: [
"$items",
3
]
}
}
}
},
{
$project: {
_id: null,
items: {
$map: {
input: {
$objectToArray: "$$ROOT"
},
as: "item",
in: "$$item.v"
}
}
}
},
{
$group: {
_id: null,
items: {
$push: "$items"
}
}
}
])
unwind the items array
group according to Tax(first position element of item using $arrayElemAt)
In project stage use map on objectToArray to get value of keys pushed into array
push the array using $group giving desired output
Output:
[
{
"_id": null,
"items": [
[
"Tax",
20,
40,
60
],
[
"FX Adjustments",
20,
40,
60
]
]
}
]

Related

distinct strings value and concatenating the results

i'm trying to distinct different string values of "comment" and "dates" and concatenate them in two different strings one for comments and one for reclamation dates I used $addToSet to distinct values but every time the concatenating is giving empty result
and this is m code :
db.collection.aggregate([
{
$group: {
_id: {
b: "$type"
},
total: {
$sum: 1
},
root: {
$push: "$$ROOT"
}
}
},
{
"$unwind": "$root"
},
{
$group: {
_id: {
r: "$root.situation",
b: "$root.type"
},
cnt: {
$sum: 1
},
total: {
"$first": "$total"
},
}
},
{
$project: {
a: [
{
k: "$_id.r",
v: "$cnt"
}
],
type: "$_id.b",
total: "$total",
_id: 0,
}
},
{
$project: {
d: {
$arrayToObject: "$a"
},
type: 1,
total: 1
}
},
{
$group: {
_id: "$type",
situation: {
$push: "$d"
},
sum: {
"$first": "$total"
},
//add to set of commets and dates
comment: {
$addToSet: "$comment"
},
daterec: {
$addToSet: "$daterec"
},
}
},
{
$project: {
_id: 0,
type: "$_id",
sum: 1,
"options": {
$mergeObjects: "$situation"
},
//concatenating results
comment: {
$reduce: {
input: "$comment",
initialValue: "",
in: {
$cond: [
{
"$eq": [
"$$value",
""
]
},
"$$this",
{
$concat: [
"$$value",
" ",
"$$this"
]
}
]
}
}
},
daterec: {
$reduce: {
input: "$daterec",
initialValue: "",
in: {
$cond: [
{
"$eq": [
"$$value",
""
]
},
"$$this",
{
$concat: [
"$$value",
" ",
"$$this"
]
}
]
}
}
}
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$$ROOT",
"$options"
]
}
}
},
{
$project: {
options: 0,
}
},
])
and this is the output :
{
"after sales management": 4,
"comment": "",
"daterec": "",
"instock": 1,
"sum": 5,
"type": "pc"
}
its always giving "comment" and"daterec": empty while i want it to show the concatenating of distinct strings it should looks something like this :
{
"after sales management": 4,
"comment": "waiting for pieces waiting for approval nothing",
"daterec": "1",//this is just an example beacause all dates are set to "1"
"instock": 1,
"sum": 5,
"type": "pc"
}
this is an example of my work with a collection example:
https://mongoplayground.net/p/dB3xBFsIfun
PS : i think the problem is in the of using the $addToSet insite $group

How to get Index(Rank) from record (with given condition) in MongoDB?

I am fetching data with aggregate(), and it's working fine. I am using collection sentimentAnalysisYTD and data in this collection :
{
"_id": "60d1c3a13bef1b1c92361313",
"review_id": "6168476",
"id": "20143",
"posted_date": "2021-01-21T00:00:00.000Z",
"user_rating": 10,
"user_id": "111410796",
"ota": "klook",
"source_market": "en_sg",
"createdAt": "2021-01-21T00:00:00.000Z",
"sentiment_class": 2,
"sentiment_score": 0.998403429985046,
"persona": [
"food & drink critic"
]
}
And now I have to get the Rank of my product (which is id as product_id, I will match with given product_id). Also, I am using $limit and $offset. How can I get my current rank with given product_id. I have tried the following code :
database.collection("productsSingleDayData").createIndex({ id: -1 })
database.collection("sentimentAnalysisYTD").aggregate([
{
$match: condition
},
{
$lookup: {
from: "productsSingleDayData",
localField: "id",
foreignField: "id",
as: "product_details"
}
},
{
"$unwind": "$product_details"
},
{
"$group":
{
_id:
{
"id": "$id"
},
csat: { "$avg": { "$ifNull": ["$user_rating", 0] } },
product_details: {
$first: "$product_details"
},
// topusers: { $push: "$$ROOT" },
totalPositiveScores: {
"$sum": {
"$cond": [{
"$eq": ["$sentiment_class", 2]
}, "$sentiment_score", 0]
}
},
totalNegativeScores: {
"$sum": {
"$cond": [{
"$eq": ["$sentiment_class", 0]
}, "$sentiment_score", 0]
}
},
totalNeturalScores: {
"$sum": {
"$cond": [{
"$eq": ["$sentiment_class", 1]
}, "$sentiment_score", 0]
}
},
total_positive:
{
$sum: {
$cond: [{
$and: [
{ "$eq": ["$sentiment_class", 2] }
/*{"$eq":["$sentiment_class","negative"] }*/
]
},
1,
0
]
}
},
total_negative:
{
$sum: {
$cond: [{
$and: [
{ "$eq": ["$sentiment_class", 0] }
]
},
1,
0
]
}
},
total_netural:
{
$sum: {
$cond: [{
$and: [
{ "$eq": ["$sentiment_class", 1] }
]
},
1,
0
]
}
},
total_reviews:
{
$sum: {
$cond: [{
$and: [
]
},
1,
0
]
}
},
}
},
{
$project: {
'totalPositiveScores': 1,
'totalNegativeScores': 1,
'total_positive': 1,
'total_netural': 1,
'total_negative': 1,
'total_reviews': 1,
"product_details": 1,
"rank": 1,
'average_positive_score': {
$cond: [{
$gt: ["$total_positive", 0]
}, {
$divide: ["$totalPositiveScores", "$total_positive"]
}, 0]
},
'average_negative_score': {
$cond: [{
$gt: ["$total_negative", 0]
}, {
$divide: ["$totalNegativeScores", "$total_negative"]
}, 0]
},
'average_netural_score': {
$cond: [{
$gt: ["$total_netural", 0]
}, {
$divide: ["$totalNeturalScores", "$total_netural"]
}, 0]
}
}
},
{
$sort: {
[req.body.sentiment_class]: -1,
}
},
{
$skip: parseInt(req.body.offset)
},
{
$limit: parseInt(req.body.limit)
},
])

How to sum values of every document based of filed name using Mongodb and NodeJs

based on other solution
I have the following data stored on my mongo instance and i am trying to sum from each document the occurrences of each word looping on all documents, so final result should be aggregation by name with sum of all occurrences.
[
{
"_id": {
"$oid": "5f972f0a7c38a0f412d88ad0"
},
"cars": 1,
"collision": 1,
"crash": 1,
"eleven": 1,
"injured": 1,
"involving": 1
},
{
"_id": {
"$oid": "5f972f0a7c38a0f412d88b96"
},
"injured": 1,
"and": 1,
"man": 1,
"attack": 1,
"ashfield": 1,
"dog": 1,
"killed": 1,
"labrador": 1,
"sutton": 1
},
{
"_id": {
"$oid": "5f972f0a7c38a0f412d88ad2"
},
"the": 1,
"'var": 1,
"accountable'": 1,
"day": 1,
"goal": 1,
"have": 1,
"lacazette's": 1,
"match": 1,
"should": 1,
"stood": 1
},
....
and i execute the following Nodejs execution using MongoDB driver:
await db.collection('occurrences').aggregate([{
$project: {
_id: 0,
fields: {
$filter: {
input: { $objectToArray: "$$ROOT" },
cond: { $eq: [
{ $type: "$$this.v"
},
"double"
] }
}
}
}
},
{
$unwind: "$fields"
},
{
$group: {
_id: "$fields.k",
total: {
$sum: "$fields.v"
}
}
},
{
$group: {
_id: null,
aggregates: {
$push:
{ k: "$_id",
v: "$total"
}
}
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$aggregates" }
}
}]).toArray(function (err, docs) {
console.log(docs)
})
now my wish is to sum all word occurrences into object as follows:
{injured: 2, attack: 1, ....}
currently i am getting an empty array while i am printing console.log
I found the mistake, in my case all i need to change is
cond: { $eq: [
{ $type: "$$this.v"
},
"double"
] }
into
cond: { $eq: [
{ $type: "$$this.v"
},
"int"
] }

Aggregation in mongodb and group by 15 min

I have a Schema in mongodb like:
INPUT
{
_id: ObjectId("5e05c1089b3e4e333cee8c39"),
name:"Alex",
activity:[
{
_id: ObjectId("5e05c1089b3e4e333cee8c39"),
type: 'run',
start_timestamp: ISODate("2020-01-11T11:34:59.804Z"),
end_timestamp: ISODate("2020-01-11T11:40:00.804Z")
},
{
_id: ObjectId("5e05c1089b3e4e333cee8c40"),
type: 'stop',
start_timestamp: ISODate("2020-01-11T11:40:00.804Z"),
end_timestamp: ISODate("2020-01-11T11:42:00.804Z")
},
{
_id: ObjectId("5e05c1089b3e4e333cee8c41"),
type: 'wait',
start_timestamp: ISODate("2020-01-11T11:42:00.804Z"),
end_timestamp: ISODate("2020-01-11T11:52:00.804Z")
},
{
_id: ObjectId("5e05c1089b3e4e333cee8c41"),
type: 'stop',
start_timestamp: ISODate("2020-01-11T11:52:00.804Z"),
end_timestamp: ISODate("2020-01-11T12:02:00.804Z")
},
{
_id: ObjectId("5e05c1089b3e4e333cee8c41"),
type: 'sleep',
start_timestamp: ISODate("2020-01-11T12:02:00.804Z"),
end_timestamp: ISODate("2020-01-11T12:48:00.804Z")
}
]
}
This is a schema for a man activity, i need to find brake-up of every 15 minute (brake-up duration in minute),i have found a solution stackoverflow but here only single timestamp but in my case there are 2 timestamp and first i have to calculate duration and then group by according to 15 minute
OUTPUT
[
{
_id: "2020-01-11T11:34 to 2020-01-11T11:49" ,
duration: "15 min",
"brake-up":{
run:"6 min",
stop:"2 min",
wait:"7 min"
}
},
{
_id: "2020-01-11T11:49 to 2020-01-11T12:04" ,
duration: 15 min,
"brake-up":{
wait:"3 min"
stop:"10 min"
sleep:"2 min"
}
{
_id: "2020-01-11T12:04 to 2020-01-11T12:19" ,
duration: 15 min,
"brake-up":{
sleep:"15 min"
}
}
]
Thanks
It's a bit tedious solution.
Explanation
I assume activity.type not repeated inside X min break-up
I assume start_timestamp and end_timestamp as is (don't ignore seconds:milliseconds)
We calculate min / max dates from start_timestamp and end_timestamp
We calculate how many 15 min breaks are between min / max dates
We create from and to variables that includes X min break-up from min / max dates
For each from and to, we filter activities
Once we filter, we calculate waste time taking in mind from and to and start_timestamp and end_timestamp dates
We create Array with activity + waste time and transform it into object
db.collection.aggregate([
{
$project: {
root: "$$ROOT",
duration: {
$toInt: 15
},
"start": {
$reduce: {
"input": "$activity",
initialValue: ISODate("2100-01-01"),
in: {
$min: [
"$$value",
"$$this.start_timestamp",
"$$this.end_timestamp"
]
}
}
},
"end": {
$reduce: {
"input": "$activity",
initialValue: ISODate("1970-01-01"),
in: {
$max: [
"$$value",
"$$this.start_timestamp",
"$$this.end_timestamp"
]
}
}
}
}
},
{
$addFields: {
interval: {
$range: [
0,
{
$round: {
$divide: [
{
$toLong: {
$subtract: [
"$end",
"$start"
]
}
},
{
$multiply: [
"$duration",
60,
1000
]
}
]
}
},
1
]
}
}
},
{
$unwind: "$interval"
},
{
$addFields: {
from: {
$add: [
"$start",
{
$multiply: [
"$interval",
{
$multiply: [
"$duration",
60,
1000
]
}
]
}
]
},
to: {
$min: [
"$end",
{
$add: [
"$start",
{
$multiply: [
{
$add: [
"$interval",
1
]
},
{
$multiply: [
"$duration",
60,
1000
]
}
]
}
]
}
]
},
activity: "$root.activity"
}
},
{
$addFields: {
activity: {
$filter: {
input: "$activity",
cond: {
$or: [
{
$and: [
{
$gte: [
"$$this.start_timestamp",
"$from"
]
},
{
$lte: [
"$$this.end_timestamp",
"$to"
]
}
]
},
{
$and: [
{
$lte: [
"$$this.start_timestamp",
"$to"
]
},
{
$gte: [
"$$this.end_timestamp",
"$from"
]
}
]
}
]
}
}
}
}
},
{
$project: {
_id: {
$concat: [
{
$toString: "$from"
},
" to ",
{
$toString: "$to"
}
]
},
name: "$root.name",
duration: {
$concat: [
{
$toString: "$duration"
},
" min"
]
},
"brake-up": {
$map: {
input: "$activity",
in: {
k: "$$this.type",
v: {
$round: {
$divide: [
{
"$subtract": [
{
$min: [
"$$this.end_timestamp",
"$to"
]
},
{
$max: [
"$$this.start_timestamp",
"$from"
]
}
]
},
{
$multiply: [
60,
1000
]
}
]
}
}
}
}
}
}
},
{
$unwind: "$brake-up"
},
{
$group: {
_id: {
_id: "$_id",
duration: "$duration",
name: "$name",
"brake-up-k": "$brake-up.k"
},
"brake-up-v": {
$sum: "$brake-up.v"
}
}
},
{
$group: {
_id: {
_id: "$_id._id",
duration: "$_id.duration",
name: "$_id.name"
},
"brake-up": {
$push: {
k: "$_id.brake-up-k",
v: {
$concat: [
{
$toString: "$brake-up-v"
},
" min"
]
}
}
}
}
},
{
$project: {
_id: "$_id._id",
name: "$_id.name",
duration: "$_id.duration",
"brake-up": {
$arrayToObject: "$brake-up"
}
}
},
{
$sort: {
_id: 1
}
}
])
MongoPlayground

Aggregate documents in MongoDB of double nested arrays

I am trying to count documents with different conditions. Here I have such simplified table of texts(documents):
{
"teamId": "1",
"stage": "0",
"answeredBy": [userId_1, userId_2],
"skippedBy": [userId_3],
"answers": []
},
{
"teamId": "1",
"stage": "0",
"answeredBy": [userId_2],
"skippedBy": [userId_1],
"answers": []
},
{
"teamId" : "1",
"stage": "0",
"answeredBy": [userId_3],
"skippedBy": [userId_2],
"answers": []
},
{
"teamId" : "1",
"stage": "1",
"answeredBy": [userId_3],
"skippedBy": [userId_1, userId_2],
"answers": [
{ "readBy": [userId_1] },
{ "readBy": [userId_1, userId_2] },
{ "readBy": [userId_3, userId_1] },
]
},
{
"teamId" : "1",
"stage": "1",
"answeredBy": [userId_3],
"skippedBy": [userId_1, userId_2],
"answers": [
{ "readBy": [userId_1] },
{ "readBy": [userId_1, userId_2] },
{ "readBy": [userId_3] },
]
};
And I want to count in one query per appropriate user Id, stage and teamID (so first $match must be per teamId and stages: "0" or "1":
how many documents on stage: "0" contains userID in answeredBy OR skippedBy arrays (I called this Document "Answered")
how many documents on stage: "0" doesn't contain userID both in answeredBy AND in skippedBy arrays (I called this Document "Unanswered")
how many documents with stage: "1" have in answers array at least ONE array readBy which doesn't contains user (I called it "UnRead" Document)
So I tried to achieve it in many ways, but the most difficult part is to iterate through nested arrays (readBy) of array answers and find which one doesn't contain appropriate user and count this document as UNREAD.
Possible results:
{
answered: 2,
unanswered: 1,
unread: 1,
};
or
[
{ _id: 'answered', count: 2 },
{ _id: 'unanswered', count: 1 },
{ _id: 'unread', count: 1 }
]
I am stuck after writing this query and don't know how to iterate through readBy arrays:
db.texts.aggregate([
{ $match: {teamId: 1, $or: [{currStage: 0}, {currStage: 1}]}},
{ $project: { 'stage': { $switch: { branches: [
{ case:
{ $and: [ { $eq: [ '$currStage', 0 ] },
{ $not: [ { $or: [ { $in: [ userId_1, '$answeredBy' ] },
{ $in: [ userId_1, '$skippedBy' ] } ] } ] } ] },
then: 'unanswered'},
{ case:
{ $and: [ { $eq: [ '$currStage', 0 ] },
{ $or: [ { $in: [ userId_1, '$answeredBy' ] },
{ $in: [ userId_1, '$skippedBy' ] } ] } ] },
then: 'answered'},
{ case:
{ $and: [ { $eq: [ '$currStage', 1 ] },
{ $not: [ { $in: [ userId_1, '$answers.readBy' ] } ] } ] },
then: 'unread'},
] } } } },
{ $group: { _id: '$stage', count: { $sum: 1 } } },
]);
try this, I am assuming userid = userId_1
db.getCollection('answers').aggregate([
{ $match: {teamId: '1', $or: [{stage: '0'}, {stage: '1'}]}},
{$project:{
counts :{$cond: [
{$or:[{$in:["userId_1", "$answeredBy"]}, {$in:["userId_1", "$skippedBy"]}]},
{$literal:{answered: 1, unaswered: 0}},
{$literal:{answered: 0, unaswered: 1}}
]},
unread : {$cond:[
{$gt:[{$reduce: {
input: "$answers",
initialValue: 1,
in: {$multiply:["$$value",
{$cond:[
{$in:["userId_1", "$$this.readBy"]},
{$literal: 0},
{$literal: 1}
]}
]}}},
0
]},
{$literal: 1},
{$literal: 0}
]}
}},
{$group: {_id: null, answered: {$sum: "$counts.answered"}, unanswered: {$sum: "$counts.unanswered"}, unread: {$sum: "$unread"}}}
])
Here is my working solution. Thank you for everyone who tried to solve it and helped me.
db.test.aggregate([
{ $match: {teamId: "1", $or: [{stage: "0"}, {stage: "1", "answers": {$elemMatch: {"readBy": {$nin: ["userId_1"]}}}}]}},
{ $project: { 'stage': { $switch: { branches: [
{ case:
{ $and: [ { $eq: [ '$stage', "0" ] },
{ $not: [ { $or: [ { $in: [ "userId_1", '$answeredBy' ] },
{ $in: [ "userId_1", '$skippedBy' ] } ] } ] } ] },
then: 'unanswered'},
{ case:
{ $and: [ { $eq: [ '$stage', "0" ] },
{ $or: [ { $in: [ "userId_1", '$answeredBy' ] },
{ $in: [ "userId_1", '$skippedBy' ] } ] } ] },
then: 'answered'},
{ case:
{ $eq: [ '$stage', "1" ] } ,
then: 'unread'},
] } } } },
{ $group: { _id: '$stage', count: { $sum: 1 } } },
])
Maybe I should find a better solution, but currently this is what I need.

Resources