How to merge multiple documents in one. MongoDB - javascript

I want to make a new document, which get all cartItems from all orders which have the same buyer. And if it hasn't a pair (like with Leonard) it's make new doc but with status "orderId" : "merged".
For example:
It's needed for situation, when some customer will make few different orders but I need to give only one consolidated recipe.
Collection orders:
Input
{
"_id" : "001",
"buyer": "Sheldon"
"cartItems" : [
{
"itemName" : "Water",
"itemPrice" : 3
}
],
"totalCost" : 3
},
{
"_id" : "002",
"buyer" : "Sheldon",
"cartItems" : [
{
"itemName" : "Milk",
"itemPrice" : 2
}
],
"totalCost" : 2
},
{
"_id" : "003",
"buyer" : "Sheldon",
"cartItems" : [
{
"itemName" : "Butter",
"itemPrice" : 4
}
],
"totalCost" : 4
},
{
"_id" : "004",
"buyer" : "Leonard",
"cartItems" : [
{
"itemName" : "Water",
"itemPrice" : 3
}
],
"totalCost" : 3
}
Output
{
"_id" : "003_new",
"buyer" : "Sheldon",
"cartItems" : [
{
"itemName" : "Water",
"itemPrice" : 3
},
{
"itemName" : "Milk",
"itemPrice" : 2
},
{
"itemName" : "Butter",
"itemPrice" : 4
}
],
"totalCost" : 9,
"orderId" : "merged"
},
{
"_id" : "004_new",
"buyer" : "Leonard",
"cartItems" : [
{
"itemName" : "Water",
"itemPrice" : 3
}
],
"totalCost" : 3,
"orderId" : "merged"
}
Would be better if you provide example in JS.

db.orders.aggregate([
{$sort: {_id: 1, buyer: 1}},
{$unwind: '$cartItems'},
{$group: {_id: '$buyer', cartItems: {$push: '$cartItems'},
totalCost: {$sum: '$totalCost'},
id: {$last: {$concat: ["$_id", "_", "new" ]}},
buyer: {$last: '$buyer'}}},
{$addFields: {orderId: 'merged', _id: '$id'}},
{$project: {"id": 0 }}])
BTW, it's mongodb shell, but it's JS ;)

Related

Get the value of specific fields in an array based on a condition

I have an array below.
For each "_id" in each object element in this array, there is an array called "form".
What I would like to get is the answerValue for each _id, where the field value in form is cState.
let array = [{
"_id" : "a1",
"user" : "John",
"form" : [
{
"question" : "question1",
"questionValue" : "Which state do you live in?",
"answers" : [
"answer1"
],
"answerValue" : [
"Arizona"
],
"field" : "cState",
},
{
"question" : "question2",
"questionValue" : "What is your nationality?",
"answers" : [
"answer2"
],
"answerValue" : [
"American"
],
"field" : "nationality",
},
"_id" : "a2",
"user" : "Mike",
"form" : [
{
"question" : "question1",
"questionValue" : "Which state do you live in?",
"answers" : [
"answer3"
],
"answerValue" : [
"Florida"
],
"field" : "cState",
},
{
"question" : "question2",
"questionValue" : "What is your nationality?",
"answers" : [
"answer2"
],
"answerValue" : [
"American"
],
"field" : "nationality",
},
}]
Expected Output
[{
"_id" : "a1",
"user" : "John",
"answerValue": "Arizona"
},
{
"_id" : "a2",
"user" : "Mike",
"answerValue": "Florida"
},
]
Here's what I tried:
let allDemographics
allDemographics = array.map((item) => {
return {
user: item.array._id,
nationality: item.array.nationality,
state: item.array.form,
}
})
Try it :
let array = [
{
"_id" : "a1",
"user" : "John",
"form" : [
{
"question" : "question1",
"questionValue" : "Which state do you live in?",
"answers" : [
"answer1"
],
"answerValue" : [
"Arizona"
],
"field" : "cState",
},
{
"question" : "question2",
"questionValue" : "What is your nationality?",
"answers" : [
"answer2"
],
"answerValue" : [
"American"
],
"field" : "nationality",
}
]
},
{
"_id" : "a2",
"user" : "Mike",
"form" : [
{
"question" : "question1",
"questionValue" : "Which state do you live in?",
"answers" : [
"answer3"
],
"answerValue" : [
"Florida"
],
"field" : "cState",
},
{
"question" : "question2",
"questionValue" : "What is your nationality?",
"answers" : [
"answer2"
],
"answerValue" : [
"American"
],
"field" : "nationality",
},
]
}
]
let allDemographics = array.map((item) => {
let fieldCstate = item.form.find(form => form.field === "cState")
return {
_id: item._id,
user: item.user,
answerValue: fieldCstate.answerValue[0],
}
})
console.log(allDemographics)
This one I like because you filter before you map. If you had a large array this might give you better performance:
let arr = array.filter(x => x.form.every(y => y.field === "cState"));
const data = arr.map((x) => ({ user: x.user, id: x._id,
answer:x.form[0].answerValue }));
console.log(data[0].answer)
Or a one liner:
let arr = array.filter(x => x.form.every(y => y.field === "cState"))
.map(x => ({ user: x.user, id: x._id, answer:x.form[0].answerValue })
)

How to group by sub documents and get unique value of value field?

This is my database collection:
{"productId" : 1,
"isVariant": 1,
"isComplete" : 1,
"variantId" : 1,
"attributeSet" : [
{
"name" : "Capacity",
"value" : "500 GB",
"id" : 3
},
{
"name" : "Form Factor",
"value" : "5 inch",
"id" : 4
},
{
"id" : 5,
"name" : "Memory Components",
"value" : "3D NAND",
"isVariation" : 0
}
]
},
{"productId" : 2,
"isVariant": 1,
"isComplete" : 1,
"variantId" : 1,
"attributeSet" : [
{
"name" : "Capacity",
"value" : "1 TB",
"id" : 3
},
{
"name" : "Form Factor",
"value" : "5 inch",
"id" : 4
},
{
"id" : 5,
"name" : "Memory Components",
"value" : "3D NAND",
"isVariation" : 0
}
]
},
{"productId" : 3,
"isVariant": 1,
"isComplete" : 0,
"variantId" : 1,
"attributeSet" : [
{
"name" : "Capacity",
"value" : "500 GB",
"id" : 3
},
{
"name" : "Form Factor",
"value" : "2.5 inch",
"id" : 4
},
{
"id" : 5,
"name" : "Memory Components",
"value" : "3D NAND",
"isVariation" : 0
}
]
},
{"productId" : 4,
"isVariant": 1,
"isComplete" : 0,
"variantId" : 1,
"attributeSet" : [
{
"name" : "Capacity",
"value" : "1 TB",
"id" : 3
},
{
"name" : "Form Factor",
"value" : "2.5 inch",
"id" : 4
},
{
"id" : 5,
"name" : "Memory Components",
"value" : "3D NAND",
"isVariation" : 0
}
]
}
Now I want to send the data of only the attribute where isVariation is not 0. Also I want to send the variant values of each attribute where isComplete =1. Hence the result should look like this
result : [{
"id": 3,
"name": "Capacity",
"value": [
"500 GB",
"1 TB"
]
}, {
"id": 4,
"name": "Form Factor",
"value": [
"5 inch"
]
}]
The above result does not have value of 2.5 inch as the isComplete is 0 for this document. Can anyone help me with the query
$match isComplete is 1
$project to show required fields
$unwind deconstruct attributeSet array
$match attributeSet.isVariation is not 0
$group by attributeSet.id and get first name and get unique value using $addToSet
db.collection.aggregate([
{ $match: { isComplete: 1 } },
{
$project: {
_id: 0,
attributeSet: 1
}
},
{ $unwind: "$attributeSet" },
{ $match: { "attributeSet.isVariation": { $ne: 0 } } },
{
$group: {
_id: "$attributeSet.id",
name: { $first: "$attributeSet.name" },
value: { $addToSet: "$attributeSet.value" }
}
}
])
Playground
The $project stage is not required in your query, i have added because this will optimize your query performance.

Add New key at the end of each object inside an array

I have data from mongodb like this from one collection.
/* 1 */
{
"_id" : ObjectId("5be94355f220b62c7449dc0f"),
"districts" : [
{
"name" : "NORTH AND MIDDLE",
"code" : 632.0
},
{
"name" : "EAST",
"code" : 603.0
},
{
"name" : "SOUTH",
"code" : 602.0
}
],
"state" : "ISLANDS"
}
/* 2 */
{
"_id" : ObjectId("5be94355f220b62c7441dc04"),
"districts" : [
{
"name" : "Apple",
"code" : 512.0
},
{
"name" : "Ball",
"code" : 522.0
}
],
"state" : "GOLD"
}
/* 3 */
{
"_id" : ObjectId("5eee07816a011d391a45178"),
"districts" : [
{
"name" : "DAM",
"code" : 478.0
},
{
"name" : "DEN",
"code" : 481.0
},
{
"name" : "DOG AND CAT",
"code" : 461.0
}
],
"state" : "THE NAGAR AND HAVELI"
}
I was given an excel sheet like below as shown with no other information only 2 columns
My work is to add "Short Name of District" for all districts.
I tried below method
var tc = [
"NORTH AND MIDDLE",
"EAST",
"SOUTH",
"Apple",
"Ball ",
"DAM ",
"DEN ",
"DOG AND CAT"
]
db.dummy.find({"districts.name":{$in:tc}}).forEach(x => {
x["districts"].forEach( y => {
if (
y.name == "NORTH AND MIDDLE" ){
y.short_name = "NAM"
}
if (
y.name == "EAST" ){
y.short_name = "ET"
}
if (
y.name == "SOUTH" ){
y.short_name = "ST"
}
})
})
I got the result
/* 1 */
{
"_id" : ObjectId("5be94355f220b62c7449dc0f"),
"districts" : [
{
"name" : "NORTH AND MIDDLE",
"code" : 632.0,
"short_name" : "NAM"
},
{
"name" : "EAST",
"code" : 603.0,
"short_name" : "ET"
},
{
"name" : "SOUTH",
"code" : 602.0,
"short_name" : "ST"
}
],
"state" : "ISLANDS"
}
/* 2 */
{
"_id" : ObjectId("5be94355f220b62c7441dc04"),
"districts" : [
{
"name" : "Apple",
"code" : 512.0,
"short_name" : "Al"
},
{
"name" : "Ball",
"code" : 522.0
"short_name" : "BA"
}
],
"state" : "GOLD"
}
/* 3 */
{
"_id" : ObjectId("5eee07816a011d391a45178"),
"districts" : [
{
"name" : "DAM",
"code" : 478.0,
"short_name" : "DA"
},
{
"name" : "DEN",
"code" : 481.0,
"short_name" : "DN"
},
{
"name" : "DOG AND CAT",
"code" : 461.0
"short_name" : "DAC"
}
],
"state" : "THE NAGAR AND HAVELI"
}
Is this is the only method ??
like using if loop for all districts or any other methods are there like using mongodb aggregate or any other javascript methods. It will be helpful if other methods are there as it will be problem to use if loop when there is 730 districts are there. I dont have experience in working with aggregate frameworks so i thought anyone might know other method.
You may write a mapping:
const districtNameToShort = {
'NORTH AND MIDDLE': 'NAM',
'EAST': 'ET',
...
}
Then in your forEach
const districtNameToShort = {
'NORTH AND MIDDLE': 'NAM',
'EAST': 'ET'
}
db.dummy.find().forEach(x => {
db.dummy.update(
{_id : x._id},
{$set: {
districts: x.districts.map(district => {
district.short_name = districtNameToShort[district.name] || district.name
return district
})
}}
)
})

How to grab recursively data from mongo db?

I have some data (let's call it logs) in mongodb, let's say like this:
{
name: String,
category_id: String
}
Each category has parent_id. What I want is to get as up tree as possible, to the first parent and get all parents for each item of data I get from so called logs.
What I thought of first: in the controller to get all projects, then recursively get it's all parents. It'll probably works, but it seems tedious and wrong.
There is probably a better thing to do on the model itself, like a static method.
So, my question is how would you do this with mongodb? I know there are aggregations, and I used them a couple of times, but I can see how to use them if by certain field with the certain value. But here you get one project, get the next by it's parent_id and so on and so on.
You have to look at $graphLookup aggregation stage. Provide a set of relevant data for more help.
EDIT : here an example :
---DATA---
#logs collection
db.logs.find({});
{
"_id" : ObjectId("5b4f2970d42ef3178d108e86"),
"name" : "01",
"category" : "cat1"
}
{
"_id" : ObjectId("5b4f2981d42ef3178d108e87"),
"name" : "02",
"category" : "cat1"
}
{
"_id" : ObjectId("5b4f298ad42ef3178d108e88"),
"name" : "03",
"category" : "cat2"
}
{
"_id" : ObjectId("5b4f2997d42ef3178d108e89"),
"name" : "04",
"category" : "cat2"
}
{
"_id" : ObjectId("5b4f29bed42ef3178d108e8a"),
"name" : "015",
"category" : "cat10"
}
#categories collection
db.categories.find({});
{
"_id" : "cat1",
"parent_id" : "cat2"
}
{
"_id" : "cat2",
"parent_id" : "cat10"
}
{
"_id" : "cat10"
}
---AGGREGATION QUERY---
db.logs.aggregate(
[
{
$graphLookup: {
from: "categories",
startWith: "$category", // connectToField value(s) that recursive search starts with
connectFromField: "parent_id",
connectToField: "_id",
as: "related_categories",
maxDepth: 10, // optional
depthField: "depthField" // optional - name of field in output documents
}
},
],
);
---OUTPUT---
{
"_id" : ObjectId("5b4f2970d42ef3178d108e86"),
"name" : "01",
"category" : "cat1",
"related_categories" : [
{
"_id" : "cat10",
"depthField" : NumberLong(2)
},
{
"_id" : "cat2",
"parent_id" : "cat10",
"depthField" : NumberLong(1)
},
{
"_id" : "cat1",
"parent_id" : "cat2",
"depthField" : NumberLong(0)
}
]
}
{
"_id" : ObjectId("5b4f2981d42ef3178d108e87"),
"name" : "02",
"category" : "cat1",
"related_categories" : [
{
"_id" : "cat10",
"depthField" : NumberLong(2)
},
{
"_id" : "cat2",
"parent_id" : "cat10",
"depthField" : NumberLong(1)
},
{
"_id" : "cat1",
"parent_id" : "cat2",
"depthField" : NumberLong(0)
}
]
}
{
"_id" : ObjectId("5b4f298ad42ef3178d108e88"),
"name" : "03",
"category" : "cat2",
"related_categories" : [
{
"_id" : "cat10",
"depthField" : NumberLong(1)
},
{
"_id" : "cat2",
"parent_id" : "cat10",
"depthField" : NumberLong(0)
}
]
}
{
"_id" : ObjectId("5b4f2997d42ef3178d108e89"),
"name" : "04",
"category" : "cat2",
"related_categories" : [
{
"_id" : "cat10",
"depthField" : NumberLong(1)
},
{
"_id" : "cat2",
"parent_id" : "cat10",
"depthField" : NumberLong(0)
}
]
}
{
"_id" : ObjectId("5b4f29bed42ef3178d108e8a"),
"name" : "015",
"category" : "cat10",
"related_categories" : [
{
"_id" : "cat10",
"depthField" : NumberLong(0)
}
]
}

Mongodb aggregate based on past date

I am trying to perform a query in Mongodb. The query I'd like to perform is find all orders in a collection based on date (7 days past), then add up the prices with the nested objects for each order. I have the following code so far:
Collection/Data
{
"_id" : "g32fYpydfSFDbFkoi",
"orderNumber" : 1234,
"createdAt" : ISODate("2016-01-12T13:50:17.559Z"),
"productsInOrder" : [
{
"category" : "ambient",
"item" : 23982,
"desc" : "Ergonomic Cotton Sausages",
"quantity" : "456",
"price" : "0.54",
"lineprice" : "246.24",
"_id" : "BdD4QnM7sYTwBpLds"
},
{
"category" : "ambient",
"item" : 15336,
"desc" : "Rustic Wooden Chicken",
"quantity" : "2",
"price" : "1.87",
"lineprice" : "3.74",
"_id" : "PvtSxi2MfYrZNTD6f"
},
{
"category" : "chilled",
"item" : 57584,
"desc" : "Unbranded Soft Chicken",
"quantity" : "3",
"price" : "4.69",
"lineprice" : "14.07",
"_id" : "ppkECqmhPvg7pQcgB"
},
{
"category" : "ambient",
"item" : 71168,
"desc" : "Rustic Rubber Computer",
"quantity" : "5",
"price" : "3.04",
"lineprice" : "15.20",
"_id" : "bZtr5dkvsG92YtLoe"
},
{
"category" : "frozen",
"item" : 87431,
"desc" : "Unbranded Granite Sausages",
"quantity" : "5678",
"price" : "1.98",
"lineprice" : "11242.44",
"_id" : "ZKur3rHhtCLsWiENr"
},
{
"category" : "frozen",
"item" : 75007,
"desc" : "Practical Frozen Towels",
"quantity" : "678",
"price" : "1.19",
"lineprice" : "806.82",
"_id" : "g78zvzoE8wJkciD9C"
},
{
"category" : "frozen",
"item" : 84721,
"desc" : "Fantastic Metal Hat",
"quantity" : "34",
"price" : "1.83",
"lineprice" : "62.22",
"_id" : "4aqxBWhXy5cabbbiM"
},
{
"category" : "frozen",
"item" : 72240,
"desc" : "Fantastic Granite Towels",
"quantity" : "1",
"price" : "2.94",
"lineprice" : "2.94",
"_id" : "MQD2LNv36mE3BWvZJ"
},
{
"category" : "chilled",
"item" : 89448,
"desc" : "Intelligent Concrete Towels",
"quantity" : "6678",
"price" : "0.42",
"lineprice" : "2804.76",
"_id" : "AjRrxFT4mfpxuciC4"
},
{
"category" : "chilled",
"item" : 57584,
"desc" : "Unbranded Soft Chicken",
"quantity" : "1111",
"price" : "4.69",
"lineprice" : "5210.59",
"_id" : "4yBspve6mBNNzqDnZ"
}
]
}
Query
Orders.aggregate([
{ $match: { 'createdAt': { $gt: pastDate }}},
{ $unwind: '$productsInOrder' },
{
$group: {
_id: null,
price: {
$sum: '$productsInOrder.price'
}
}
}
]);
What I ultimately want is to output the total price per day for the last 7 days. Can anyone help point me in the right direction? Many thanks in advance.
Firstly, the $sum operator will ignore non-numeric values and the productsInOrder.price subdocument field is of String type so it would be best if you convert this to a numeric field.
Having done that, to output the total price per day for the last 7 days, change the group by key to use the $dayOfMonth operator which groups your documents per day within that 7 day range, as in the following
Orders.aggregate([
{ "$match": { "createdAt": { "$gt": pastDate } } },
{ "$unwind": "$productsInOrder" },
{
"$group": {
"_id": {
"day": { "$dayOfMonth": "$createdAt" }
},
"price": { "$sum": "$productsInOrder.price" }
}
}
]);

Categories