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.
Related
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
I am attempting an left antijoin on these two collections.
I want all users where department is equal to 'IT' that aren't in a meeting that had an endAt time > 175. Either as a creator or receiver.
So essentially whoever hasn't been in a meeting in the last xxx time.
Based on below collections:
John would be retrieved because he is apart of department IT and has not been a receiver or creator after '175'.
Jane has an endAt time after 175 and is in IT so wouldn't be retrieved
Bill is apart of finance so even though he hasn't been it doesn't matter
Bob has an endAt time after 175 and is in IT so wouldn't be retrieved
Mary is in IT and has not been in any meetings so she is retrieved.
Users Collection:
[
{
_id: ObjectId("1"),
name: "john",
department: 'IT'
},
{
_id: ObjectId("2"),
name: "jane",
department: 'IT'
},
{
_id: ObjectId("3"),
name: "bill",
department: 'finance'
},
{
_id: ObjectId("4"),
name: "Bob",
department: 'IT'
},
{
_id: ObjectId("5"),
name: "Mary",
department: 'IT'
}
]
Meetings Collection:
[
{
_id: ObjectId("a"),
endedAt: 100,
creator_id: ObjectId("1"),
receiver_id: ObjectId("2")
},
{
_id: ObjectId("b"),
endedAt: 150,
creator_id: ObjectId("1"),
receiver_id: ObjectId("3")
},
{
_id: ObjectId("c"),
endedAt: 200,
creator_id: ObjectId("4"),
receiver_id: ObjectId("2")
},
{
_id: ObjectId("d"),
endedAt: 250,
creator_id: ObjectId("2"),
receiver_id:
}
]
Output:
[
{
_id: ObjectId("1"),
name: "john",
department: 'IT'
},
{
_id: ObjectId("5"),
name: "Mary",
department: 'IT'
}
]
My approach:
db.users.aggregate([
{
$match:
{
type: 'IT'
}
},
{
$lookup:
{
from: "meetings",
let:
{
userid: "$_id",
},
pipeline: [
{ $match:
{ $expr:
{
$and:[
{
$or: [
{ $eq: ["$receiver_id", "$$userid"] },
{ $eq: ["$creator_id", "$$userid"] },
]
},
{ $gt: ["$endAt", 175] }
]
}
}
}
],
as: "result"
}
},
{
$unwind:
{
path: "$result",
preserveNullAndEmptyArrays: true
}
},
{
$match:
{
result: {$exists:false}
}
}
])
Query
match "IT"
join if >175 AND (userid in any of the 2 (creator/receiver))
*its lookup pipeline, because multiple join creteria
reject those that are joined
Test code here
db.users.aggregate([
{
"$match": {
"department": {
"$eq": "IT"
}
}
},
{
"$lookup": {
"from": "meetings",
"let": {
"userid": "$_id"
},
"pipeline": [
{
"$match": {
"$expr": {
"$and": [
{
"$gt": [
"$endedAt",
175
]
},
{
"$or": [
{
"$eq": [
"$$userid",
"$creator_id"
]
},
{
"$eq": [
"$$userid",
"$receiver_id"
]
}
]
}
]
}
}
},
{
"$project": {
"_id": 1
}
}
],
"as": "meetings"
}
},
{
"$match": {
"$expr": {
"$eq": [
"$meetings",
[]
]
}
}
},
{
"$unset": [
"meetings"
]
}
])
aggregate
db.users.aggregate([
{
"$match": {
department: "IT"
}
},
{
"$lookup": {
"from": "meeting",
"localField": "_id",
"foreignField": "creator_id",
"as": "meeting_creator"
}
},
{
"$lookup": {
"from": "meeting",
"localField": "_id",
"foreignField": "receiver_id",
"as": "meeting_receiver"
}
},
{
"$match": {
"$and": [
{
"meeting_creator.endedAt": {
"$not": {
"$gt": 175
}
}
},
{
"meeting_receiver.endedAt": {
"$not": {
"$gt": 175
}
}
}
]
}
},
{
"$project": {
_id: 1,
name: 1,
department: 1
}
}
])
data
db={
"users": [
{
_id: "1",
name: "john",
department: "IT"
},
{
_id: "2",
name: "jane",
department: "IT"
},
{
_id: "3",
name: "bill",
department: "finance"
},
{
_id: "4",
name: "Bob",
department: "IT"
},
{
_id: "5",
name: "Mary",
department: "IT"
}
],
"meeting": [
{
_id: "a",
endedAt: 100,
creator_id: "1",
receiver_id: "2"
},
{
_id: "b",
endedAt: 150,
creator_id: "1",
receiver_id: "3"
},
{
_id: "c",
endedAt: 200,
creator_id: "4",
receiver_id: "2"
},
{
_id: "d",
endedAt: 250,
creator_id: "2",
receiver_id: ""
}
]
}
result
[
{
"_id": "1",
"department": "IT",
"name": "john"
},
{
"_id": "5",
"department": "IT",
"name": "Mary"
}
]
mongoplayground
This is the solution I came up with that ended up working, does anyone have any details what would be the most efficient?
db.users.aggregate([
{
$match:
{
type: 'IT'
}
},
{
$lookup:
{
from: "meetings",
let:
{
userid: "$_id",
},
pipeline: [
{ $match:
{ $expr:
{
$and:[
{
$or: [
{ $eq: ["$receiver_id", "$$userid"] },
{ $eq: ["$creator_id", "$$userid"] },
]
},
{ $gt: ["$endAt", 175] }
]
}
}
}
],
as: "result"
}
},
{
$unwind:
{
path: "$result",
preserveNullAndEmptyArrays: true
}
},
{
$match:
{
result: {$exists:false}
}
}
])
consider the following document skeleton
{
_id: "615749dce3438547adfff9bc",
items: [
{
type: "shirt",
color: "red",
sizes: [
{
label: "medium",
stock: 10,
price: 20,
},
{
label: "large",
stock: 30,
price: 40,
}
]
},
{
type: "shirt",
color: "green",
sizes: [
{
label: "small",
stock: 5,
price: 3,
},
{
label: "medium",
stock: 5,
price: 3,
},
]
}
]
}
when a new item comes in, I want to insert a new document to items, unless an item exists with the same type and color as the new one, in this case I want only to merge sizes into that existing item's sizes.
sizes does not have to be unique.
I tried to use $push with upsert: true and arrayFilters but apparently $push ignores arrayFilters.
node with mongodb package.
Query1
filter to see if exists
if exists map to update, else add in the end
*2 array reads, but stil faster than query2
Test code here
db.collection.update({},
[
{
"$set": {
"newitem": {
"type": "shirt",
"color": "red",
"sizes": [
{
"label": "medium"
}
]
}
}
},
{
"$set": {
"found": {
"$ne": [
{
"$filter": {
"input": "$items",
"cond": {
"$and": [
{
"$eq": [
"$$this.type",
"$newitem.type"
]
},
{
"$eq": [
"$$this.color",
"$newitem.color"
]
}
]
}
}
},
[]
]
}
}
},
{
"$set": {
"items": {
"$cond": [
{
"$not": [
"$found"
]
},
{
"$concatArrays": [
"$items",
[
"$newitem"
]
]
},
{
"$map": {
"input": "$items",
"in": {
"$cond": [
{
"$and": [
{
"$eq": [
"$$this.type",
"$newitem.type"
]
},
{
"$eq": [
"$$this.color",
"$newitem.color"
]
}
]
},
{
"$mergeObjects": [
"$$this",
{
"sizes": {
"$concatArrays": [
"$$this.sizes",
"$newitem.sizes"
]
}
}
]
},
"$$this"
]
}
}
}
]
}
}
},
{
"$unset": [
"found",
"newitem"
]
}
])
Query2
(alternative solution)
reduce and do the update
if found keep the updated, else add in the end
*1 array read (but concat is slow, for big arrays, >500 members, if you have big arrays use query1)
*this is the normal way to do it, if we had a fast way to add in the end of the array, but we dont, so Query1 is faster
Test code here
db.collection.update({},
[
{
"$set": {
"newitem": {
"type": "shirt",
"color": "red",
"sizes": [
{
"label": "medium"
}
]
}
}
},
{
"$set": {
"items-found": {
"$reduce": {
"input": "$items",
"initialValue": {
"items": [],
"found": null
},
"in": {
"$cond": [
{
"$and": [
{
"$eq": [
"$$value.found",
null
]
},
{
"$eq": [
"$$this.type",
"$newitem.type"
]
},
{
"$eq": [
"$$this.color",
"$newitem.color"
]
}
]
},
{
"items": {
"$concatArrays": [
"$$value.items",
[
{
"$mergeObjects": [
"$$this",
{
"sizes": {
"$concatArrays": [
"$$this.sizes",
"$newitem.sizes"
]
}
}
]
}
]
]
},
"found": true
},
{
"items": {
"$concatArrays": [
"$$value.items",
[
"$$this"
]
]
},
"found": "$$value.found"
}
]
}
}
}
}
},
{
"$set": {
"items": {
"$cond": [
"$items-found.found",
"$items-found.items",
{
"$concatArrays": [
"$items-found.items",
[
"$newitem"
]
]
}
]
}
}
},
{
"$unset": [
"items-found",
"newitem"
]
}
])
Imagine the is a document like this:
{
_id: ObjectID('someIdblahbla')
users: [
{
_id: 'id1',
name: 'name1',
},
{
_id: 'id2',
name: 'name2',
},
{
_id: 'id3',
name: 'name3'
}
]
}
I have an array like this:
const newData = [
{_id: 'id1', name: 'newName1'},
{_id: 'id2', 'name': 'newName2', family:'newFamily2'}
]
what I want is to update the array in the document using the corresponding _id and using it to add/update each element.
so my end result would be like:
{
_id: ObjectID('someIdblahbla')
users: [
{
_id: 'id1',
name: 'newName1',
},
{
_id: 'id2',
name: 'newName2',
family:'newFamily2'
},
{
_id: 'id3',
name: 'name3'
}
]
}
my guess was using The filtered positional operator but I am not sure if it's the correct way to go and how to do it.
thank you for your kind tips beforehand.
There is no straight way to add/update in array, you can use update with aggregation pipeline starting from MongoDB 4.2,
First of all, you need to convert _id from string to objectId type, if you are using mongoose npm you can use mongoose.Types.ObjectId method or if you are using mongodb npm you can use ObjectId method,
let newData = [
{ _id: 'id1', name: 'newName1' },
{ _id: 'id2', 'name': 'newName2', family:'newFamily2' }
];
let newIds = [];
newData = newData.map(n => {
n._id = ObjectId(n._id); // or mongoose.Types.ObjectId(n._id)
newIds.push(n._id); // for checking conditions
return n;
});
You can put query condition, and do below operations,
$map to iterate loop of users array, check condition if user._id is in input newIds then do update operation otherwise do insert operation
update operation:
$filter to iterate loop of input newData and filter already present object from input so we can update it
$arrayElemAt to get first object from above filtered array
$mergeObjects to merge current object with above input object
insert operation:
$filter to iterate loop of newData array and return not present object means new items in array of objects
$concatArrays to concat above new and updated result array
db.collection.updateOne(
{ _id: ObjectId("someIdblahbla") },
[{
$set: {
users: {
$concatArrays: [
{
$map: {
input: "$users",
as: "u",
in: {
$cond: [
{ $in: ["$$u._id", newIds] },
{
$mergeObjects: [
"$$u",
{
$arrayElemAt: [
{
$filter: {
input: newData,
cond: { $eq: ["$$this._id", "$$u._id"] }
}
},
0
]
}
]
},
"$$u"
]
}
}
},
{
$filter: {
input: newData,
cond: { $not: { $in: ["$$this._id", "$users._id"] } }
}
}
]
}
}
}]
)
Playground
Query1 (update(merge objects) existing members, doesn't add new members)
Test code here
Replace
[{"_id": "id1","name": "newName1"},{"_id": "id2","name": "newName2","family": "newFamily2"}] with you array or the driver variable that hold the array
db.collection.update({
"_id": {
"$eq": "1"
}
},
[
{
"$addFields": {
"users": {
"$map": {
"input": "$users",
"as": "user",
"in": {
"$reduce": {
"input": [
{
"_id": "id1",
"name": "newName1"
},
{
"_id": "id2",
"name": "newName2",
"family": "newFamily2"
}
],
"initialValue": "$$user",
"in": {
"$let": {
"vars": {
"old_user": "$$value",
"new_user": "$$this"
},
"in": {
"$cond": [
{
"$eq": [
"$$old_user._id",
"$$new_user._id"
]
},
{
"$mergeObjects": [
"$$old_user",
"$$new_user"
]
},
"$$old_user"
]
}
}
}
}
}
}
}
}
}
])
Query2 (update(merge) if found, else push in the end)
Its like the above but finds the not-existing members,and push them in the end.Its a bit more slower and complicated
Test code here
Replace
[{"_id": "id1","name": "newName1"},{"_id": "id2","name": "newName2","family": "newFamily2"},{"_id": "id4","name": "newName4"}]
with your array or the driver variable that hold the array
db.collection.update({
"_id": {
"$eq": "1"
}
},
[
{
"$addFields": {
"yourarray": [
{
"_id": "id1",
"name": "newName1"
},
{
"_id": "id2",
"name": "newName2",
"family": "newFamily2"
},
{
"_id": "id4",
"name": "newName4"
}
]
}
},
{
"$addFields": {
"new-ids": {
"$setDifference": [
{
"$map": {
"input": "$yourarray",
"as": "u",
"in": "$$u._id"
}
},
{
"$map": {
"input": "$users",
"as": "u",
"in": "$$u._id"
}
}
]
}
}
},
{
"$addFields": {
"users": {
"$concatArrays": [
{
"$map": {
"input": "$users",
"as": "user",
"in": {
"$reduce": {
"input": "$yourarray",
"initialValue": "$$user",
"in": {
"$let": {
"vars": {
"old_user": "$$value",
"new_user": "$$this"
},
"in": {
"$cond": [
{
"$eq": [
"$$old_user._id",
"$$new_user._id"
]
},
{
"$mergeObjects": [
"$$old_user",
"$$new_user"
]
},
"$$old_user"
]
}
}
}
}
}
}
},
{
"$filter": {
"input": "$yourarray",
"as": "u",
"cond": {
"$in": [
"$$u._id",
"$new-ids"
]
}
}
}
]
}
}
},
{
"$unset": [
"yourarray",
"new-ids"
]
}
])
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 } }
])