How can I do an inner join of two collections in mongodb - 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

Related

How to do mongodb inner join and grouping

// 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

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

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

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