Calculate discount in mongodb - 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

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

insert multiple objects into nested arrays with condition

I'm a mongo beginner and struggling to insert multiple objects into multiple nested array in one document.
The document looks like this:
[
{
"id": 1,
"name": "myObject",
"sections": [
{
"id": "section1",
"items": [
{
"id": 1,
"name": "item1.1",
"scores": [
{
"userId": 13,
"score": 10
}
]
},
{
"id": 2,
"name": "item1.2",
"scores": [
{
"userId": 66,
"score": 10
}
]
}
]
},
{
"id": "section2",
"items": [
{
"id": 3,
"name": "item2.1",
"scores": [
{
"userId": 13,
"score": 20
}
]
}
]
}
]
}
]
I now want to insert new scores for userId=10 for every item in every section.
The score is of course different for every item.
let's assume scores like this (all for userId=10)
[
{
"sectionId": "section1"
"itemName: "item1.1"
"score: 10,
"userId": 10
},
{
"sectionId": "section1"
"itemName: "item1.2"
"score: 15,
"userId": 10
},
{
"sectionId": "section2"
"itemName: "item2.1"
"score: 33,
"userId": 10
}
]
Added for clarification
the updated document should look like the following.
{
"id": 1,
"name": "myObject",
"sections": [
{
"id": "section1",
"items": [
{
"id": 1,
"name": "item1.1",
"scores": [
{
"userId": 13,
"score": 10
},
{ // <-- newly added score
"userId": 10,
"score": 10
}
]
},
{
"id": 2,
"name": "item1.2",
"scores": [
{
"userId": 66,
"score": 10
},
{ // <- newly added score
"userId": 10,
"score": 15
}
]
}
]
}
// the remaining document is omitted for brevity but the above should also be applied to this sections
so far I have been able to achieve what I want for one single score like this
db.collection.update({
id: 1
},
{
$push: {
"sections.$[item].items.$[score].scores": {
"userId": 10,
"score": 13
},
}
},
{
arrayFilters: [
{
"score.userId": {
$ne: 10
}
},
{
"item.name": {
$eq: "item1.1"
}
}
]
})
This inserts a score for userId=10 in itemName=item1.1 if no score for userId=10 exists.
But I'm struggling on how to insert multiple scores into multiple items.
I saw that you can merge objects together, so maybe this would be an option although it kinda fells like an overkill.
So how can I insert all my scores for the different items in one atomic operation?
EDIT: Added clarification about the desired result.
Query
pipeline update, requires MongoDB >= 4.2
reduce on the data that you want to insert, with initial value the sections
nested 3 maps that always do the same
if its not the key-value i want, keep the old value
else (merge {:newkey (map ...)})
if userId exists updates its score, else insert new userID and score
query assumes that there is a score array even if empty, i mean it
only creates new userId+score, not sections items etc
*you can avoid the set/unset and use driver variables in all the places where data is used
PlayMongo
db.collection.update({},
[
{
"$set": {
"data": [
{
"sectionId": "section1",
"itemName": "item1.1",
"userId": 10,
"score": 20
},
{
"sectionId": "section1",
"itemName": "item1.1",
"userId": 13,
"score": 30
},
{
"sectionId": "section2",
"itemName": "item2.1",
"score": 33,
"userId": 10
}
]
}
},
{
"$set": {
"sections": {
"$reduce": {
"input": "$data",
"initialValue": "$sections",
"in": {
"$let": {
"vars": {
"data": "$$this"
},
"in": {
"$map": {
"input": "$$value",
"in": {
"$cond": [
{
"$ne": [
"$$section.id",
"$$data.sectionId"
]
},
"$$section",
{
"$mergeObjects": [
"$$section",
{
"items": {
"$map": {
"input": "$$section.items",
"in": {
"$cond": [
{
"$ne": [
"$$item.name",
"$$data.itemName"
]
},
"$$item",
{
"$mergeObjects": [
"$$item",
{
"scores": {
"$let": {
"vars": {
"user_exist": {
"$in": [
"$$data.userId",
"$$item.scores.userId"
]
}
},
"in": {
"$cond": [
{
"$not": [
"$$user_exist"
]
},
{
"$concatArrays": [
"$$item.scores",
[
{
"userId": "$$data.userId",
"score": "$$data.score"
}
]
]
},
{
"$map": {
"input": "$$item.scores",
"in": {
"$cond": [
{
"$ne": [
"$$score.userId",
"$$data.userId"
]
},
"$$score",
{
"$mergeObjects": [
"$$score",
{
"score": "$$data.score"
}
]
}
]
},
"as": "score"
}
}
]
}
}
}
}
]
}
]
},
"as": "item"
}
}
}
]
}
]
},
"as": "section"
}
}
}
}
}
}
}
},
{
"$unset": [
"data"
]
}
])

MongoDB Aggregate and Group by Subcategories of products

I have a MongoDB schema that looks like this
const ProductModel = new Schema({
subcategory: {
type : mongoose.Schema.Types.ObjectId,
ref : "Subcategory",
},
product_name: {
type: String
},
description: {
type: String
},
price: {
type: Number
},
});
And a subcategory schema:
const SubcategoryModel = new Schema({
subcategoryName: {
type: String,
}
});
The input query before aggregation looks like this:
[
{
"_id": "111",
"subcategory": {
"_id": "456",
"categoryName": "Sneakers",
},
"product_name": "Modern sneaker",
"description": "Stylish",
"price": 4400
},
{
"_id": "222",
"subcategory": {
"_id": "456",
"categoryName": "Sneakers",
},
"product_name": "Blue shoes",
"description": "Vived colors",
"price": 7500
},
{
"_id": "333",
"subcategory": {
"_id": "123",
"categoryName": "Jackets",
"__v": 0
},
"product_name": "Modern jacket",
"description": "Stylish",
"price": 4400
},
}
]
The final result of the query should look like this:
{
"Sneakers":[
{
"product_name":"Modern sneaker",
"description":"Stylish",
"price":"4400"
},
{
"product_name":"Blue shoes",
"description":"Vived colors",
"price":"7500"
},
"Jackets":{
"...."
}
]
}
Subcategory before aggregation:
"subcategories": [
{
"_id": "123",
"categoryName": "Jackets",
},
{
"_id": "456",
"categoryName": "Sneakers",
}
]
I'm trying to populate the subcategory, And then group the products by their subcategoryName field.
You can use this aggregation query:
First $lookup to do the join between Product and Subcategory creating the array subcategories.
Then deconstructs the array using $unwind.
$group by the name of subproduct adding the entire object using $$ROOT.
The passes the fields you want using $project.
And replaceRoot to get key value into arrays as Sneakers and Jackets.
db.Product.aggregate([
{
"$lookup": {
"from": "Subcategory",
"localField": "subcategory.categoryName",
"foreignField": "categoryName",
"as": "subcategories"
}
},
{
"$unwind": "$subcategories"
},
{
"$group": {
"_id": "$subcategories.categoryName",
"data": {
"$push": "$$ROOT"
}
}
},
{
"$project": {
"data": {
"product_name": 1,
"description": 1,
"price": 1
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$arrayToObject": [
[
{
"k": "$_id",
"v": "$data"
}
]
]
}
}
}
])
Example here
With your provided data, result is:
[
{
"Sneakers": [
{
"description": "Stylish",
"price": 4400,
"product_name": "Modern sneaker"
},
{
"description": "Vived colors",
"price": 7500,
"product_name": "Blue shoes"
}
]
},
{
"Jackets": [
{
"description": "Stylish",
"price": 4400,
"product_name": "Modern jacket"
}
]
}
]

Aggregation pipeline to lookup and merge nested documents

I am struggling with writing an aggregation pipeline to lookup nested documents by their _id and return a specific name without overwriting the existing keys/values in the data. I have managed to do this for the nested array, but am unable to do it for an array that is nested within the nested array.
So I want to lookup the _id of each ingredient and each subIngredient and merge them with the data for these ingredients that already exists (i.e. qty, measure).
Here is what I have so far:
https://mongoplayground.net/p/ft4oIMm_8wg
Products Collection:
[
{
"_id": {
"$oid": "5ecf269bceb735416db0b329"
},
"id": 36,
"title": "Product 1",
"description": {
"generalInformation": "Some information",
"activeIngredients": [
{
"_id": 1636,
"qty": 133.5,
"measure": "µg",
"subIngredient": [
{
"_id": 1626,
"qty": 16.6,
"measure": "µg"
}
],
},
{
"_id": 1234,
"qty": 133.5,
"measure": "µg",
"subIngredient": [
{
"_id": 1122,
"qty": 16.6,
"measure": "µg"
},
{
"_id": 1212,
"qty": 16.6,
"measure": "µg"
}
],
},
]
},
},
{
"_id": {
"$oid": "5ecf269bceb735416db0b346"
},
"id": 36,
"title": "Product 2",
"description": {
"generalInformation": "Some information",
"activeIngredients": [
{
"_id": 1234,
"qty": 133.5,
"measure": "µg",
"subIngredient": [
{
"_id": 1122,
"qty": 16.6,
"measure": "µg"
}
],
},
{
"_id": 1234,
"qty": 133.5,
"measure": "µg",
"subIngredient": [
{
"_id": 1122,
"qty": 16.6,
"measure": "µg"
},
{
"_id": 1212,
"qty": 16.6,
"measure": "µg"
}
],
},
]
},
}
]
Ingredients Collection:
[
{
"_id": 1234,
"name": "Ingredient 1",
},
{
"_id": 1122,
"name": "Ingredient 2",
},
{
"_id": 1212,
"name": "Ingredient 3",
},
{
"_id": 1636,
"name": "Ingredient 4",
},
{
"_id": 1626,
"name": "Ingredient 5",
}
]
What should be returned:
[
{
"_id": ObjectId("5ecf269bceb735416db0b329"),
"title": "Product 1"
"description": {
"activeIngredients": {
"_id": 1636,
"measure": "µg",
"name": "Ingredient 4",
"qty": 133.5,
"subIngredient": [
{
"_id": 1626,
"measure": "µg",
"qty": 16.6
}
]
},
"generalInformation": "Some information"
},
"ingredients": [
{
"_id": 1636,
"measure": "µg",
"name": "Ingredient 4",
"qty": 133.5,
"subIngredient": [
{
"_id": 1626,
"measure": "µg",
"qty": 16.6,
"name": "Ingredient 2"
}
]
},
{
"_id": 1234,
"measure": "µg",
"name": "Ingredient 1",
"qty": 133.5,
"subIngredient": [
{
"_id": 1122,
"measure": "µg",
"qty": 16.6,
"name": "Ingredient 2"
},
{
"_id": 1212,
"measure": "µg",
"qty": 16.6,
"name": "Ingredient 2"
}
]
}
]
},
]
My current pipeline:
[
{
"$unwind": {
"path": "$description.activeIngredients",
"preserveNullAndEmptyArrays": false
}
},
{
"$lookup": {
"from": "ingredients",
"localField": "description.activeIngredients._id",
"foreignField": "_id",
"as": "description.activeIngredients.name"
}
},
{
"$addFields": {
"description.activeIngredients.name": {
"$arrayElemAt": [
"$description.activeIngredients.name.name",
0
]
}
}
},
{
"$group": {
"_id": "$_id",
"ingredients": {
"$push": "$description.activeIngredients"
},
"description": {
"$first": "$description"
},
"title": {
"$first": "$title"
}
}
},
{
"$lookup": {
"from": "ingredients",
"localField": "ingredients.subIngredient._id",
"foreignField": "_id",
"as": "subIngredients"
}
}
]
Appreciate any help. Thanks.
You're not far off and you can achieve this result in multiple different ways, one of which is to just $unwind the subingredients array, $lookup on that and finally adding another $group stage to restructure the document.
As you've clearly shown you know how to do all these things i'll show a different way that utilizes operators like $map, $indexOfArray and Mongo's v3.6 $lookup syntax.
The strategy is instead of unwinding the subarray we just $lookup all the relevant sub-ingredients and then "merge" the two arrays using the operators i specified.
i.e taking:
[ {id: 5, name: "name"} ];
[ {id: 5, qty: 25} ]
And making them into:
[ {id: 5, name: "name", qty: 25} ]
Here's how we do it:
db.products.aggregate([
{
"$unwind": {
"path": "$description.activeIngredients",
"preserveNullAndEmptyArrays": false
}
},
{
"$lookup": {
"from": "ingredients",
"localField": "description.activeIngredients._id",
"foreignField": "_id",
"as": "description.activeIngredients.name"
}
},
{
"$addFields": {
"description.activeIngredients.name": {
"$arrayElemAt": [
"$description.activeIngredients.name.name",
0
]
}
}
},
{
"$lookup": {
"from": "ingredients",
"let": {
sub: "$description.activeIngredients.subIngredient"
},
"pipeline": [
{
$match: {
$expr: {
"$setIsSubset": [
[
"$_id"
],
{
$map: {
input: "$$sub",
as: "datum",
in: "$$datum._id"
}
}
]
}
}
}
],
"as": "subIngredients"
}
},
{
"$addFields": {
"description.activeIngredients.subIngredient": {
$map: {
input: "$description.activeIngredients.subIngredient",
as: "sub",
in: {
"$mergeObjects": [
"$$sub",
{
name: {
$arrayElemAt: [
"$subIngredients.name",
{
"$indexOfArray": [
"$subIngredients._id",
"$$sub._id"
]
}
]
}
}
]
}
}
}
}
},
{
"$group": {
"_id": "$_id",
"ingredients": {
"$push": "$description.activeIngredients"
},
"description": {
"$first": "$description"
},
"title": {
"$first": "$title"
}
}
}
])
MongoPlayground

Get the $size (length) of a nested array and calculate the difference to a stored value on the parent object - using aggregate

Let's consider that I have the following documents (ignoring the _id):
[
{
"Id": "Store1",
"Info": {
"Location": "Store1 Street",
"PhoneNumber": 111
},
"MaxItemsPerShelf": 3,
"Shelf": [
{
"Id": "Shelf1",
"Items": [
{
"Id": "Item1",
"Name": "bananas"
},
{
"Id": "Item2",
"Name": "apples"
},
{
"Id": "Item3",
"Name": "oranges"
}
]
},
{
"Id": "Shelf2",
"Items": [
{
"Id": "Item4",
"Name": "cookies"
},
{
"Id": "Item5",
"Name": "chocolate"
}
]
},
{
"Id": "Shelf3",
"Items": []
}
]
},
{
"Id": "Store3",
"Info": {
"Location": "Store2 Street",
"PhoneNumber": 222
},
"MaxItemsPerShelf": 2,
"Shelf": [
{
"Id": "Shelf4",
"Items": [
{
"Id": "Item6",
"Name": "champoo"
},
{
"Id": "Item7",
"Name": "toothpaste"
}
]
},
{
"Id": "Shelf5",
"Items": [
{
"Id": "Item8",
"Name": "chicken"
}
]
}
]
}
]
Given a specific Shelf.Id I want to get the following result ( Shelf.Id = "Shelf2"):
[{
"Info": {
"Location": "Store1 Street",
"PhoneNumber": 111
},
"ItemsNumber": 2,
"ItemsRemaining": 1
}]
Therefore:
ItemsNumberis the $size of Shelf
and
ItemsRemainingis equal to MaxItemsPerShelf $size of Shelf
also I want to copy the value of the Info to the aggregate output.
How can I accomplish this with aggregate? On my efforts I couldn't pass through an iterator that gets the $size of $Shelf.Items
You can use below aggregation
db.collection.aggregate([
{ "$match": { "Shelf.Id": "Shelf2" }},
{ "$replaceRoot": {
"newRoot": {
"$let": {
"vars": {
"shelf": {
"$filter": {
"input": {
"$map": {
"input": "$Shelf",
"in": {
"Id": "$$this.Id",
"count": { "$size": "$$this.Items" }
}
}
},
"as": "ss",
"cond": { "$eq": ["$$ss.Id", "Shelf2"] }
}
}
},
"in": {
"Info": "$Info",
"ItemsNumber": { "$arrayElemAt": ["$$shelf.count", 0] },
"ItemsRemaining": {
"$subtract": [
"$MaxItemsPerShelf",
{ "$ifNull": [
{ "$arrayElemAt": ["$$shelf.count", 0] },
0
]}
]
}
}
}
}
}}
])