Calculate running total across for different groups by day - mongodb

I'm trying to aggreate a collection of transactions into a running total of owners by day.
The initial collection looks like this:
[
{ "to": "A", "from": "0", "ts": 1 },
{ "to": "A", "from": "0", "ts": 1 },
{ "to": "B", "from": "0", "ts": 1 },
{ "to": "B", "from": "0", "ts": 2 },
{ "to": "C", "from": "0", "ts": 3 },
{ "to": "A", "from": "B", "ts": 4 }
]
What I would like to get is something like this:
[
{
"ts": 1,
"holdings": [
{ "owner": "0", "holdings": -3 },
{ "owner": "A", "holdings": 2 },
{ "owner": "B", "holdings": 1 }
]
},
{
"ts": 2,
"holdings": [
{ "owner": "0", "holdings": -4 },
{ "owner": "A", "holdings": 2 },
{ "owner": "B", "holdings": 2 }
]
},
{
"ts": 4,
"holdings": [
{ "owner": "0", "holdings": -5 },
{ "owner": "A", "holdings": 3 },
{ "owner": "B", "holdings": 1 },
{ "owner": "C", "holdings": 1 }
]
}
]
I've already understood how to generate this for a single ts that I'm setting, but I don't know how to do it across all ts.
The aggregation pipeline for a single ts looks like this:
db.collection.aggregate([
// start with: { "to": "A", "from": "0", "ts": 1 }
{
// create a doc with an array with subset of fields:
// { "_id": ObjectId("5a934e000102030405000000"),
// "data": [ { "change": 1, "owner": "A", "ts": "1" },
// { "change": -1, "owner": "0", "ts": "1" } ] }
$project: {
data: [
{
owner: '$to',
ts: '$ts',
change: 1,
},
{
owner: '$from',
ts: '$ts',
change: -1,
},
],
},
},
{
// unwind the array into 2 docs:
// { "_id": ObjectId("5a934e000102030405000000"), "data": { "change": 1, "owner": "A", "ts": "1" } },
// { "_id": ObjectId("5a934e000102030405000000"), "data": { "change": -1, "owner": "0", "ts": "1" } },
$unwind: '$data',
},
{
// use data as root:
// { "data": { "change": 1, "owner": "A", "ts": "1" } },
// { "data": { "change": -1, "owner": "0", "ts": "1" } }
$replaceRoot: {
newRoot: '$data',
},
},
{
// select day to calc totals
$match: {
ts: {
$lt: 6,
},
},
},
{
// sum totals, grouped by owner
$group: {
_id: '$owner',
//_id: null,
holdings: {
$sum: '$change',
},
},
},
])
This gives the correct result for a particular day (selected in the match stage). I don't understand how I can now generalize that to all days.

One way to do it is using $setWindowFields, which has a built-in accumulation:
db.collection.aggregate([
{
$project: {
ts: "$ts",
data: [{owner: "$to", change: 1}, {owner: "$from", change: -1}]
}
},
{$unwind: "$data"},
{
$group: {
_id: {ts: "$ts", owner: "$data.owner"},
holdings: {$sum: "$data.change"}
}
},
{
$setWindowFields: {
partitionBy: "$_id.owner",
sortBy: {"_id.ts": 1},
output: {
cumulativeHoldings: {
$sum: "$holdings",
window: {documents: ["unbounded", "current"]}
}
}
}
},
{
$group: {
_id: "$_id.ts",
holdings: {$push: {owner: "$_id.owner", holdings: "$cumulativeHoldings"}}
}
}
])
Playground

Related

How can I do an inner join of two collections in mongodb

// orders collection
[
{
"id": 1,
"orderName": "a",
"seqId": 100,
"etc": [],
"desc": [],
},
{
"id": 2,
"orderName": "b",
"seqId": 200,
"etc": [],
"desc": []
},
{
"id": 3,
"orderName": "c",
"seqId": 100,
"etc": [],
"desc": [],
},
]
// goods collection
[
{
"id": 1,
"title": "example1",
"items": [
{
"id": 10,
"details": [
{
"id": 100
},
{
"id": 101,
}
]
},
{
"id": 20,
"details": [
{
"id": 102,
},
{
"id": 103,
}
]
},
]
},
[
{
"id": 2,
"title": "example2",
"items": [
{
"id": 30,
"details": [
{
"id": 200
},
{
"id": 201
}
]
},
{
"id": 40,
"details": [
{
"id": 202
},
{
"id": 203
}
]
},
]
},
]
When the value of the seqId field of the document whose etc field and desc field arrays of the orders collection are empty and the value of the "goods.details.id field of the goods collection are the same, I want to get the following output. How can I do that?
[
{orderName: "a", title: "example1"},
{orderName: "b", title: "example2"},
{orderName: "c", title: "example1"},
]
Additionally, I would like to perform a sum operation based on the title of the goods
collection.
[
{"example1": 2},
{"example2": 1}
]
Simply perform a $lookup between orders.seqId and goods.items.details.id. Use $unwind to eliminate empty lookups(i.e. inner join behaviour). Finally, do a $group with $sum to get the count.
db.orders.aggregate([
{
"$match": {
"etc": [],
"desc": []
}
},
{
"$lookup": {
"from": "goods",
"localField": "seqId",
"foreignField": "items.details.id",
"pipeline": [
{
$project: {
_id: 0,
title: 1
}
}
],
"as": "goodsLookup"
}
},
{
"$unwind": "$goodsLookup"
},
{
$group: {
_id: "$goodsLookup.title",
cnt: {
$sum: 1
}
}
}
])
Mongo Playground

MongoDB Select By Group along with that Count Unique match exclude array and object fields Get data sort by latest objects

I have a collection where from the backend user can input multiple same name bikes but with different registration number but in front-End I want them to be grouped by matching the same name but as user updates separately display image changes but I want only one display image as it is 1 vehicle
provided there is a node created I will implement it we can sort it by the latest and take the price and image of it
Activa -2 Count
KTM -1 Count
but there is a catch.
Activa 2 bikes but I want only count 2 and the price as it is the same in an array I want only 1 and the same applies to displayimage here display image file path is different but I want the latest one only Sharing data below
Data:
[
{
"price": [
{
"Description": "Hourly",
"Price": "1"
},
{
"Description": "Daily",
"Price": "11"
},
{
"Description": "Monthly",
"Price": "111"
}
],
"_id": "62e69ee3edfe4d0f3cb4994a",
"bikename": "KTM",
"bikenumber": "KA05HM2034",
"bikebrand": {
"id": 1,
"label": "Honda"
},
"freekm": 234,
"displayimage": {
"file": "bike-2020-honda-city-exterior-8-1659281111883.jpg",
"file_path": "https://www.example.com/images/upload/bike-2020-honda-city-exterior-8-1659281111883.jpg",
"idx": 1
}
},
{
"price": [
{
"Description": "Hourly",
"Price": "1"
},
{
"Description": "Daily",
"Price": "11"
},
{
"Description": "Monthly",
"Price": "111"
}
],
"_id": "62dba8418ef8f51f454ed757",
"bikename": "Activa",
"bikenumber": "KA05HM2033",
"bikebrand": {
"id": 1,
"label": "Honda"
},
"freekm": 234,
"displayimage": {
"file": "bike-v_activa-i-deluxe-1658562557459.jpg",
"file_path": "https://www.example.com/images/upload/bike-v_activa-i-deluxe-1658562557459.jpg",
"idx": 0
}
},
{
"price": [
{
"Description": "Hourly",
"Price": "1"
},
{
"Description": "Daily",
"Price": "11"
},
{
"Description": "Monthly",
"Price": "111"
}
],
"_id": "62d7ff7e70b9ab38c6ab0cb1",
"bikename": "Activa",
"bikenumber": "KA05HM2223",
"bikebrand": {
"id": 1,
"label": "Honda"
},
"freekm": 234,
"afterfreekmprice": 22,
"descreption": "Activa",
"displayimage": {
"file": "bike-v_activa-i-deluxe-1658322798414.jpg",
"file_path": "https://www.example.com/images/upload/bike-v_activa-i-deluxe-1658322798414.jpg",
"idx": 0
}
}
]
Expected:
[
{
"_id":{
"price": [
{
"Description": "Hourly",
"Price": "1"
},
{
"Description": "Daily",
"Price": "11"
},
{
"Description": "Monthly",
"Price": "111"
}
],
"_id": "62dba8418ef8f51f454ed757",
"bikename": "Activa",
"bikebrand": {
"id": 1,
"label": "Honda"
},
"freekm": 234,
"displayimage": {
"file": "bike-v_activa-i-deluxe-1658562557459.jpg",
"file_path": "https://www.example.com/images/upload/bike-v_activa-i-deluxe-1658562557459.jpg",
"idx": 0
}
},
"count": 2
},
{
"_id":{
"price": [
{
"Description": "Hourly",
"Price": "1"
},
{
"Description": "Daily",
"Price": "11"
},
{
"Description": "Monthly",
"Price": "111"
}
],
"_id": "62e69ee3edfe4d0f3cb4994a",
"bikename": "KTM",
"bikebrand": {
"id": 1,
"label": "Honda"
},
"freekm": 234,
"displayimage": {
"file": "bike-2020-honda-city-exterior-8-1659281111883.jpg",
"file_path": "https://www.example.com/images/upload/bike-2020-honda-city-exterior-8-1659281111883.jpg",
"idx": 1
}
}
"count": 1
}
]
You can use the aggregation pipeline,
$sort by _id in descending order
$group by bikename and get the first root document that is latest one in root and count total documents in count
$project to show required documents
db.collection.aggregate([
{ $sort: { _id: -1 } },
{
$group: {
_id: "$bikename",
root: { $first: "$$ROOT" },
count: { $sum: 1 }
}
},
{
$project: {
_id: "$root",
count: 1
}
}
])
Playground
You can use $group for this:
db.collection.aggregate([
{$group: {
_id: "$bikename",
count: {$sum: 1},
data: {$first: "$$ROOT"}
}
},
{$set: {"data.count": "$count"}},
{$replaceRoot: {newRoot: "$data"}}
])
See how it works on the playground example

Adding a nested value as a field - MongDB aggregation

So I have a parent document with users, as well as an array that has users too. I want to add the DisplayName from the nested users array to the aggregation output. Any ideas?
Output I'm looking to achieve:
[
{
"user": {
"_id": "11",
"Name": "Dave",
"DocID": "1",
"DocDisplyName": "ABC"
},
{
"user": {
"_id": "33",
"Name": "Henry",
"DocID": "1",
"DocDisplyName": "ABC",
"BranchDisplayName:"BranchA"
}
}
]
And so on.. So an array of all users and for users that belong to a branch, add the branch display Name to the output.
// Doc 1
{
"_id": "1",
"DisplayName": "ABC",
"Users": [
{ "_id": "11", "Name": "Dave" },
{ "_id": "22", "Name": "Steve" }
],
"Branches": [
{
"_id": "111",
"DisplayName": "BranchA",
"Users": [
{ "_id": "33", "Name": "Henry" },
{ "_id": "44", "Name": "Josh" },
],
},
{
"_id": "222",
"DisplayName": "BranchB",
"Users": [
{ "_id": "55", "Name": "Mark" },
{ "_id": "66", "Name": "Anton" },
],
}
]
}
``Doc 2
{
"_id": "2",
"DisplayName": "DEF",
"Users": [
{ "_id": "77", "Name": "Josh" },
{ "_id": "88", "Name": "Steve" }
],
"Branches": [
{
"_id": "333",
"DisplayName": "BranchA",
"Users": [
{ "_id": "99", "Name": "Henry" },
{ "_id": "10", "Name": "Josh" },
],
},
{
"_id": "444",
"DisplayName": "BranchB",
"Users": [
{ "_id": "112", "Name": "Susan" },
{ "_id": "112", "Name": "Mary" },
],
}
]
}
Collection.aggregate([
{
$addFields: {
branchUsers: {
$reduce: {
input: "$Branches.Users",
initialValue: [],
in: {
$concatArrays: ["$$this", "$$value"],
},
},
},
},
},
{
$addFields: {
user: {
$concatArrays: ["$branchUsers", "$Users"],
},
},
},
{
$addFields: {
"user.DocID": "$_id","user.DocDisaplyName": "$DisplayName"
},
},
{
$unwind: "$user",
},
{
$project: {
_id: 0,
user: 1,
},
}
])
Thanks in advance!
OK I found a solution.
{
$addFields: {
"branchUsers.BranchDisplayName": {
$let: {
vars: {
first: {
$arrayElemAt: [ "$Branches", 0 ]
}
},
in: "$$first.DisplayName"
}
}
}
},
This creates the field only for the users that belong to the branch

MongoDB inner join with specific condition from both collections

I have got two collections, chapters and courses with one-to-one association,
db={
"chapters": [
{
"_id": 10,
"course_id": 1,
"author": "John"
},
{
"_id": 20,
"course_id": 2,
"author": "John"
},
{
"_id": 30,
"course_id": 3,
"author": "Timmy"
},
{
"_id": 40,
"course_id": 4,
"author": "John"
},
],
"courses": [
{
"_id": 1,
"published": true,
"name": "course 1"
},
{
"_id": 2,
"published": false,
"name": "course 2"
},
{
"_id": 3,
"published": true,
"name": "course 3"
},
{
"_id": 4,
"published": true,
"name": "course 4"
}
]
}
How do I query all chapters with the published course (published=true) and the author is "John"?
You can use $match, $lookup then $group by author then simply $filter it based on published
db.chapters.aggregate([
{
$match: {
author: "John"
}
},
{
"$lookup": {
"from": "courses",
"localField": "course_id",
"foreignField": "_id",
"as": "course"
}
},
{
$project: {
_id: 1,
course: {
$first: "$course"
},
author: 1
}
},
{
$group: {
_id: "$author",
courseChapters: {
$push: "$$ROOT"
}
}
},
{
$project: {
courseChapters: {
$filter: {
input: "$courseChapters",
as: "cc",
cond: {
$eq: [
"$$cc.course.published",
true
]
}
}
}
}
}
])
Output
[
{
"_id": "John",
"courseChapters": [
{
"_id": 10,
"author": "John",
"course": {
"_id": 1,
"name": "course 1",
"published": true
}
},
{
"_id": 40,
"author": "John",
"course": {
"_id": 4,
"name": "course 4",
"published": true
}
}
]
}
]
Mongoplayground: https://mongoplayground.net/p/x-XghXzZUk4

MongoDB, How to associate array fields for statistics

Example JSON:
{
"groups": [
{
"_id": 1,
"name": "g1"
},
{
"_id": 2,
"name": "g2"
}
],
"items": [
{
"_id": 1,
"name": "item1",
"gid": 1
},
{
"_id": 2,
"name": "item2",
"gid": 2
}
]
}
How to associate two arrays and count ?I tried to use aggregate, I didn't get the results I wanted.
Required Result:
Or can directly find all the items associated with it, perfect....
{"groups": [
{
"_id": 1,
"name": "g1",
"count": 1,
"items": [
{
"_id": 1,
"name": "item1"
}
]
},
{
"_id": 2,
"name": "g2",
"count": 1,
"items": [
{
"_id": 2,
"name": "item2"
}
]
}
]}
db.getCollection('collection').aggregate([
{$unwind:{
path:"$groups",
preserveNullAndEmptyArrays:true
}},
{$unwind:{
path:"$items",
preserveNullAndEmptyArrays:true
}},
{$redact: {$cond: [{
$eq: [
"$groups._id",
"$items.gid"
]
},
"$$KEEP",
"$$PRUNE"
]
}
},
{$project:{
_id:1,
groups_id:"$groups._id",
group_name:"$groups.name",
item_data:{
_id:"$items._id",
name:"$items.name",
}
}},
{
$group:{
_id:"$groups_id",
name:{$first:"$group_name"},
count:{$sum:1},
items:{$push:"$item_data"}
}
}
])