distinct strings value and concatenating the results - node.js

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

Related

Find the number of consecutive days that users are regularly active on MongoDB?

I want to find last the number of maximum consecutive number of days for user id.
userId(string) active_date(string) Note: Today (2022-02-20)
------------------------------------------
{ "userId": "DbdBve", "day": "2022-02-20" }
{ "userId": "DbdBve", "day": "2022-02-19" }
{ "userId": "DbdBve", "day": "2022-02-18" }
{ "userId": "DbdBve", "day": "2022-02-17" } <- Gap here | so user's been active for the last 3 days
{ "userId": "DbdBve", "day": "2022-02-15" }
userId(string) active_date(string)
------------------------------------------
{ "userId": "Gj6WEth", "day": "2022-02-20" }
{ "userId": "Gj6WEth", "day": "2022-02-15" } <- Gap here | so user's been active for the last 1 days
{ "userId": "Gj6WEth", "day": "2022-02-14" }
{ "userId": "Gj6WEth", "day": "2022-02-13" }
using mongodb v5:
first convert date to numeric value using $toLong
then using $setWindowFields extract the user active_days ranges
mongoplayground
db.collection.aggregate([
{
"$addFields": {
"active_date": {
"$toLong": "$active_date"
}
}
},
{
$setWindowFields: {
partitionBy: "$user_id",
sortBy: {
active_date: 1
},
output: {
days: {
$push: "$active_date",
window: {
range: [
-86400000, // one day in millisecond
0
]
}
}
}
}
},
{
"$set": {
"days": {
"$cond": [
{
"$gt": [
{
"$size": "$days"
},
1
]
},
0,
1
]
}
}
},
{
$setWindowFields: {
partitionBy: "$user_id",
sortBy: {
active_date: 1
},
output: {
count: {
$sum: "$days",
window: {
documents: [
"unbounded",
"current"
]
}
}
}
}
},
{
"$group": {
"_id": {
user_id: "$user_id",
count: "$count"
},
"active_days": {
$sum: 1
},
"to": {
"$max": "$active_date"
},
"from": {
"$min": "$active_date"
}
}
}
])
and at the end get the latest active_day range by adding these two stages:
{
"$sort": {
to: -1
}
},
{
"$group": {
"_id": "$_id.user_id",
"last_active_days": {
"$first": "$active_days"
}
}
}
older versions of mongodb using $reduce
mongoplayground
db.collection.aggregate([
{
$sort: {
active_date: 1
}
},
{
"$group": {
_id: "$user_id",
dates: {
"$push": {
"$toLong": "$active_date"
},
},
from: {
$first: {
"$toLong": "$active_date"
}
},
to: {
$last: {
"$toLong": "$active_date"
}
}
}
},
{
$project: {
active_days: {
$let: {
vars: {
result: {
$reduce: {
input: "$dates",
initialValue: {
prev: {
$subtract: [
"$from",
86400000
]
},
range: {
from: "$from",
to: 0,
count: 0
},
ranges: []
},
in: {
$cond: [
{
$eq: [
{
$subtract: [
"$$this",
"$$value.prev"
]
},
86400000
]
},
{
prev: "$$this",
range: {
from: "$$value.range.from",
to: "$$value.range.to",
count: {
$add: [
"$$value.range.count",
1
]
}
},
ranges: "$$value.ranges"
},
{
ranges: {
$concatArrays: [
"$$value.ranges",
[
{
from: "$$value.range.from",
to: "$$value.prev",
count: "$$value.range.count"
}
]
]
},
range: {
from: "$$this",
to: "$to",
count: 1
},
prev: "$$this"
},
]
}
}
}
},
in: {
$concatArrays: [
"$$result.ranges",
[
"$$result.range"
]
]
}
}
}
}
},
{
"$project": {
active_days: {
"$last": "$active_days.count"
}
}
}
])

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"
] }

how to return count value for two match condition by grouping in mongoose?

I wanted my output json to look like the below,
[ {
"_id": "IT",
"count 1": 1,
"count 2": 1,
},
{
"_id": "CSE",
"count1": 1,
"count2": 2,
},
]
I tried the below query in mongoose
{
$match: {
riskstatus: { $in: ["Closed", "Deffered"] },
riskstatus: { $in: [ "Open"] },
}
},
{
"$group": {
"_id": "$phase",
"count1": { "$sum": 1 },
"count2": { "$sum": 1 },
},
}
my input collection is
{"_id":"5df5ca73bb1c4526948e2421","comment":"test","status":"Open","phase":"CSE"}
{"_id":"5df5ca73bb1c4526948e2422","comment":"test","status":"closed","phase":"IT"}
{"_id":"5df5ca73bb1c4526948e2422","comment":"test","status":"Closed","phase":"CSE"}
{"_id":"5df5ca73bb1c4526948e2422","comment":"test","status":"Open","phase":"IT"}
{"_id":"5df5ca73bb1c4526948e2422","comment":"test","status":"Open","phase":"CSE"}
How to retrieve two count values for the same group with the above matching condition to achieve this result?
Latest I tried is
Risk.aggregate([
{ $match: { $or: [ { status: {$in: ["Closed", "Deffered"]} }, { status: {$in: ["Open"] } } ] } },
{
"$group": {
"_id": "$phase",
" count1": { "$sum": 1 },
"count2": { "$sum": 1 },
},
}
Please use that query
db.posts.aggregate(
{
$group: {
_id: { 'id': '$phase', 'status': '$status' },
count_1: { '$sum': 1 },
}
},
{ $project: { '_id': 0, 'depart': '$_id.id', 'status': '$_id.status', 'count_1': 1 } }
, {
$group: {
_id: '$depart',
data: {
'$push': {
'depart': '$depart',
'count_1': {
$cond: [{ $eq: ["$status", 'Open'] }, '$count_1', 0]
},
'count_2': {
$cond: [{ $eq: ["$status", 'Closed'] }, '$count_1', 0]
}
,
'count_3': {
$cond: [{ $eq: ["$status", 'Deffered'] }, '$count_1', 0]
}
},
}
}
},
{ $project:
{'name':'$_id','_id':0,
openCount: { $max: "$data.count_1"},
Othercount: {
$add:[
{$max: "$data.count_2"},
{$max: "$data.count_3"}]
}
}}
)

Compare and add two array inside nested arrays

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

Group by date in mongoose/mongodb?

Note: Mongo version 3.6.2.
I have a document that looks like this:
const Ticket = new mongoose.Schema({
event_id: [
{
required: false,
type: mongoose.Schema.Types.ObjectId,
ref: 'Event'
}
],
ticket_type: String,
createdAt: String,
}, { collection: 'tickets' });
I want to do a mongodb groupBy on ticket_type and createdAt where an event_id = X. So the output should like this:
[{ ticket_type: 'VIP', date: 2011-11-11, count: 12}, {..}]
The hard part is that createdAt is stored as timemillis in a string, ex:
{
_id : ObjectId(123),
ticket_type: 'VIP',
createdAt: '1233434',
event_id: [ObjectId(345)]
}
The answer should look something like this:
Ticket.find({ event_id: req.params.event_id }, function(err, tickCount) {
if (err) {
console.log(err);
} else {
res.json(tickCount);
}
});
Any help is greatly appreciated. Thanks.
This is what I came up with:
Ticket.aggregate([
{ $match: { event_id: ObjectId(req.body.event_id)} },
{
$addFields: {
"createdAt": {
$reduce: {
"input": {
$map: { // split string into char array so we can loop over individual characters
"input": {
$range: [ 0, { $strLenCP: "$createdAt" } ] // using an array of all numbers from 0 to the length of the string
},
"in":{
$substrCP: [ "$createdAt", "$$this", 1 ] // return the nth character as the mapped value for the current index
}
}
},
"initialValue": { // initialize the parser with a 0 value
"n": 0, // the current number
"sign": 1, // used for positive/negative numbers
"div": null, // used for shifting on the right side of the decimal separator "."
"mult": 10 // used for shifting on the left side of the decimal separator "."
}, // start with a zero
"in": {
$let: {
"vars": {
"n": {
$switch: { // char-to-number mapping
branches: [
{ "case": { $eq: [ "$$this", "1" ] }, "then": 1 },
{ "case": { $eq: [ "$$this", "2" ] }, "then": 2 },
{ "case": { $eq: [ "$$this", "3" ] }, "then": 3 },
{ "case": { $eq: [ "$$this", "4" ] }, "then": 4 },
{ "case": { $eq: [ "$$this", "5" ] }, "then": 5 },
{ "case": { $eq: [ "$$this", "6" ] }, "then": 6 },
{ "case": { $eq: [ "$$this", "7" ] }, "then": 7 },
{ "case": { $eq: [ "$$this", "8" ] }, "then": 8 },
{ "case": { $eq: [ "$$this", "9" ] }, "then": 9 },
{ "case": { $eq: [ "$$this", "0" ] }, "then": 0 },
{ "case": { $and: [ { $eq: [ "$$this", "-" ] }, { $eq: [ "$$value.n", 0 ] } ] }, "then": "-" }, // we allow a minus sign at the start
{ "case": { $eq: [ "$$this", "." ] }, "then": "." }
],
default: null // marker to skip the current character
}
}
},
"in": {
$switch: {
"branches": [
{
"case": { $eq: [ "$$n", "-" ] },
"then": { // handle negative numbers
"sign": -1, // set sign to -1, the rest stays untouched
"n": "$$value.n",
"div": "$$value.div",
"mult": "$$value.mult",
},
},
{
"case": { $eq: [ "$$n", null ] }, // null is the "ignore this character" marker
"then": "$$value" // no change to current value
},
{
"case": { $eq: [ "$$n", "." ] },
"then": { // handle decimals
"n": "$$value.n",
"sign": "$$value.sign",
"div": 10, // from the decimal separator "." onwards, we start dividing new numbers by some divisor which starts at 10 initially
"mult": 1, // and we stop multiplying the current value by ten
},
},
],
"default": {
"n": {
$add: [
{ $multiply: [ "$$value.n", "$$value.mult" ] }, // multiply the already parsed number by 10 because we're moving one step to the right or by one once we're hitting the decimals section
{ $divide: [ "$$n", { $ifNull: [ "$$value.div", 1 ] } ] } // add the respective numerical value of what we look at currently, potentially divided by a divisor
]
},
"sign": "$$value.sign",
"div": { $multiply: [ "$$value.div" , 10 ] },
"mult": "$$value.mult"
}
}
}
}
}
}
}
}
}, {
$addFields: { // fix sign
"createdAt": { $multiply: [ "$createdAt.n", "$createdAt.sign" ] }
}
},
{
$group: {
_id: {
ticket_type: "$ticket_type",
createdAt: { $dateToString: { format: "%Y-%m-%d", date: { $add: [ new Date(0), "$createdAt" ] }} },
count: { $sum: 1 }
}
}
},
{ $sort: { "createdAt": 1 } }
], function(err, tickCount) {
if (err) {
console.log(err);
} else {
res.json(tickCount);
}
});
You can use $group aggregation pipeline stage. To convert string to number you can use $toLong operator and then you can use $add (works for ISODate as well) to add that value to the date with zero miliseconds (new Date(0)) to get ISODate. Try:
Ticket.aggregate([
{ $match: { event_id: req.params.event_id } },
{
$group: {
_id: {
ticket_type: "$ticket_type",
createdAt: { $add: [ new Date(0), { $toLong: "$createdAt" } ] }
}
}
}
], function(err, tickCount) {
if (err) {
console.log(err);
} else {
res.json(tickCount);
}
});
EDIT: assuming that you manually converted string to number you can run following query to aggregate on date part of ISODate:
db.col.aggregate([
{ $match: { event_id: req.params.event_id } },
{
$group: {
_id: {
ticket_type: "$ticket_type",
createdAt: { $dateToString: { format: "%Y-%m-%d", date:{ $add: [ new Date(0), "$createdAt" ] } } },
count: { $sum: 1 }
}
}
},
{ $sort: { "_id.createdAt": 1 } }
])

Resources