How to do mongodb inner join and grouping - mongodb

// orders
[
{
"id": 1,
"orderName": "a",
"seqId": 100,
"etc": [],
"desc": [],
},
{
"id": 2,
"orderName": "b",
"seqId": 200,
"etc": [],
"desc": []
},
{
"id": 3,
"orderName": "c",
"seqId": 100,
},
]
// 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 etc field and desc field arrays of the orders collection are empty, or the non-empty document's seqId field value and the goods collection's "goods.details.id field value are the same.
I want to express the sum operation based on the title of the product collection and the sum if it is not empty.
{example1: 1, total: 2}
{example2: 1, total: 1}
For example, "example1" and "example2" represent the sum of the cases where the etc and desc field arrays are empty (the title of the goods collection), and the total represents the total regardless of whether the array is empty or not.
If so, it should be marked aboveas:

Following our discussion here, we can remove the early filtering for the 2 empty arrays and move it to a conditional sum at the $group stage.
db.orders.aggregate([
{
"$lookup": {
"from": "goods",
"localField": "seqId",
"foreignField": "items.details.id",
"as": "goodsLookup"
}
},
{
"$unwind": "$goodsLookup"
},
{
$group: {
_id: "$goodsLookup.title",
emptySum: {
$sum: {
"$cond": {
"if": {
$and: [
{
$eq: [
"$desc",
[]
]
},
{
$eq: [
"$etc",
[]
]
}
]
},
"then": 1,
"else": 0
}
}
},
total: {
$sum: 1
}
}
}
])
Mongo 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

Calculate running total across for different groups by day

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

Get all matching subdocuments and add property from parent document

Let's say i have following documents:
[
{
"key": 1,
"sub": [
{
"id": 4,
"value": 23
},
{
"id": 1,
"value": 24
}
]
},
{
"key": 2,
"sub": [
{
"id": 1,
"value": 92
},
{
"id": 2,
"value": 93
}
]
},
{
"key": 4,
"sub": [
{
"id": 3,
"value": 22
},
{
"id": 2,
"value": 43
}
]
}
]
I now want to find subdocuments by their id and also see the corresponding parent property key. I have tried following query:
db.collection.aggregate([
{
"$match": {
"sub.id": 1
}
},
{
"$addFields": {
"value": {
"$filter": {
"input": "$sub",
"cond": {
$eq: [
"$$this.id",
1
]
}
}
}
}
},
{
"$project": {
sub: 0
}
}
])
This essentially returns the right information:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"key": 1,
"value": [
{
"id": 1,
"value": 24
}
]
},
{
"_id": ObjectId("5a934e000102030405000001"),
"key": 2,
"value": [
{
"id": 1,
"value": 92
}
]
}
]
But it is not formatted how I need it and also adding more properties from the subdocuments is annoying because of the $addFields. I would rather have it formatted like this:
[
{
"id": 1,
"value": 24,
"key": 1
},
{
"id": 1,
"value": 92,
"key": 2
}
]
So I can have just an array of the matching subdocuments with additional parent properties added.
How would I do that?
Mongo Playground
After you have filtered out the unwanted subdocuments, $reduce over the response to build the subdocuments you want to see:
{"$project": {
_id: 0,
"sub": {
$reduce: {
input: {
"$filter": {
"input": "$sub",
"cond": {$eq: ["$$this.id", 1]}
}
},
initialValue: [],
in: {
$concatArrays: [
[{
key: "$key",
id: "$$this.id",
value: "$$this.value"
}],
"$$value"
]
}
}
}
}
}
Playground

Calculate discount in mongodb

I'd like to post some products to the body and get back the calculated total amount.
But it's getting complicated for me when need to apply discount in that form:
for every $amount of $product the price reduced to $new-price
(let's say every banana is 1$, if customer buy 3 then price is 2$ (but they can buy as many..))
How can I achieve that?
data
db={
"orders": [
{
"_id": "1",
"customer_id": "1",
"items": [
{
"product_id": "1",
"quantity": 2
},
{
"product_id": "2",
"quantity": 5
}
]
}
],
"product": [
{
"product_id": "1",
"name": "apple",
"price": 2,
"quantity": 1,
"free": 0
},
{
"product_id": "2",
"name": "banana",
"price": 1,
"quantity": 3,
"free": 1
}
]
}
aggregate
db.orders.aggregate([
{
"$match": {
_id: "1"
}
},
{
"$unwind": "$items"
},
{
"$lookup": {
"from": "product",
"localField": "items.product_id",
"foreignField": "product_id",
"as": "product_docs"
}
},
{
"$set": {
"product_doc": {
"$first": "$product_docs"
}
}
},
{
"$project": {
"total_each": {
"$multiply": [
{
$subtract: [
"$items.quantity",
{
"$multiply": [
{
$floor: {
$divide: [
"$items.quantity",
"$product_doc.quantity"
]
}
},
"$product_doc.free"
]
}
]
},
"$product_doc.price"
]
}
}
},
{
"$group": {
"_id": "$_id",
"total": {
"$sum": "$total_each"
}
}
}
])
result:
apple no discount, banana buy 3 get 1 free
2x2 + {5-[floor(5/3)x1]}x1 = 8
[
{
"_id": "1",
"total": 8
}
]
mongoplayground

MongoDB count values of multible nested documents

My data looks something like that:
[
{
"_id": 1,
"members": [
{
"id": 1,
"name": "name_1",
"assigned_tasks": [
1,
2,
3
]
},
{
"id": 1,
"name": "name_2",
"assigned_tasks": [
1
]
}
],
"tasks": [
{
"id": 1,
"name": "task_1",
},
{
"id": 2,
"name": "task_2",
},
{
"id": 3,
"name": "task_3",
}
]
}
]
I have a collection that represents a "class" which contains a list of members and a list of projects.
Each member can be assigned to multiple projects.
I wanna be able to count the number of members assigned to each of the tasks in the results and add it as a new field like:
[
{
"_id": 1,
"members": [
{
"id": 1,
"name": "name_1",
"assigned_tasks": [
1,
2,
3
]
},
{
"id": 1,
"name": "name_2",
"assigned_tasks": [
1
]
}
],
"tasks": [
{
"id": 1,
"name": "task_1",
"number_of_assigned_members":2
},
{
"id": 2,
"name": "task_2",
"number_of_assigned_members":1
},
{
"id": 3,
"name": "task_3",
"number_of_assigned_members":2
}
]
}
]
How can I create that query?
You can use $map and than $reduce,
$map tasks through object by object check in $reduce on members, if assigned_tasks is available or not, if available then add 1 otherwise 0,
db.collection.aggregate([
{
$addFields: {
tasks: {
$map: {
input: "$tasks",
as: "t",
in: {
$mergeObjects: [
"$$t",
{
number_of_assigned_members: {
$reduce: {
input: "$members",
initialValue: 0,
in: {
$cond: [
{ $in: ["$$t.id", "$$this.assigned_tasks"] },
{ $add: ["$$value", 1] },
"$$value"
]
}
}
}
}
]
}
}
}
}
}
])
Playground