how to transform data using aggregate function mongodb - mongodb

How to transform data base on parent_id within self join ? Is this possible make the result as expected. Please help on this thanks
db={
post: [
{
"_id": ObjectId("59f9c5629f75813e21a6fe34"),
"parent_id": "0",
"name": "main_category",
"short_desc": "",
"long_desc": "",
"slug": "main_category",
"status": true,
"createdAt": ISODate("2017-11-01T13:00:18.714Z"),
"updatedAt": ISODate("2019-02-19T07:31:20.967Z")
},
{
"_id": ObjectId("59f9c5629f75813e21a6fe73"),
"parent_id": "59f9c5629f75813e21a6fe34",
"name": "sub_category",
"short_desc": "",
"long_desc": "",
"slug": "sub_category",
"status": true,
"createdAt": ISODate("2017-11-01T13:00:18.714Z"),
"updatedAt": ISODate("2019-02-19T07:31:20.967Z")
},
{
"_id": ObjectId("59f9c5629f75813e21a6fe33"),
"parent_id": "59f9c5629f75813e21a6fe73",
"name": "sub_category1",
"short_desc": "",
"long_desc": "",
"slug": "sub_category1",
"status": true,
"createdAt": ISODate("2017-11-01T13:00:18.714Z"),
"updatedAt": ISODate("2019-02-19T07:31:20.967Z")
}
]
}
output should like this. If any more category does not belongs to anything it should stay blank
[
{
mainCategory: 'main_category',
subCategory1: 'sub_category',
subCategory2: 'sub_category1',
subCategory3: '',
subCategory4: '',
subCategory5: ''
}, {
mainCategory: '{if any}',
subCategory1: '{if any}',
subCategory2: '{if any}',
subCategory3: '',
subCategory4: '',
subCategory5: ''
}
];
Any hope to get this stat. ?

$graphLookup reads from the collection specified by its from argument, not from the documents in the pipeline.
In the pipeline you created to change the datatype, use a $merge stage to update the existing documents:
db.post.aggregate([
{$addFields: {
parent_oid: {
$cond: {
if: {$eq: ["$parent_id","0"]},
then: "$parent_id",
else: {$toObjectId: "$parent_id"}
}
}
}
},
{$merge: "post"}
])
Then you can use $graphLookup to form the lists, and transform them to the shape you need:
db.post.aggregate([
{$match: {parent_id: "0" }},
{"$graphLookup": {
"from": "post",
"startWith": "$_id",
"connectFromField": "_id",
"connectToField": "parent_oid",
"as": "response"
}},
{$unwind: "$response"},
{$group: {
_id: "$_id",
main_category: {$first: "$slug"},
subCategories: {$push: {
k: "$response.name",
v: "$response.slug"
}}
}
},
{$replaceRoot: {
newRoot: {
$mergeObjects: [
{mainCategory: "$main_category"},
{$arrayToObject: "$subCategories"}
]
}
}}
])
Output from the sample data:
[
{
"mainCategory": "main_category",
"sub_category": "sub_category",
"sub_category1": "sub_category1"
}
]
Playground

Related

How to replace array of object containing ids with the data using MongoDB aggregation

I am having a collection which contains the data like the following and want to have the desirable output which I have mentioned below.
db={
collectionA: [
{
"id": ObjectId("63b7c24c06ebe7a8fd11777b"),
"uniqueRefId": "UUID-2023-0001",
"products": [
{
"productIndex": 1,
"productCategory": ObjectId("63b7c24c06ebe7a8fd11777b"),
"productOwners": [
ObjectId("63b7c2fd06ebe7a8fd117781")
]
},
{
"productIndex": 2,
"productCategory": ObjectId("63b7c24c06ebe7a8fd11777b"),
"productOwners": [
ObjectId("63b7c2fd06ebe7a8fd117781"),
ObjectId("63b7c12706ebe7a8fd117778")
]
},
{
"productIndex": 3,
"productCategory": "",
"productOwners": ""
}
]
}
],
collectionB: [
{
"_id": ObjectId("63b7c2fd06ebe7a8fd117781"),
"fullname": "Jim Corbett",
"email": "jim.corbett#pp.com"
},
{
"_id": ObjectId("63b7c12706ebe7a8fd117778"),
"fullname": "Carry Minatti",
"email": "carry.minatty#pp.com"
},
]
}
Desirable Output = [
{
"id": ObjectId("507f1f77bcf86cd799439011"),
"uniqueRefId": "UUID-2023-0001",
"products": [
{
"productIndex": 1,
"productCategory": ObjectId('614g2f77bff86cd755439021'),
"productOwners": [
{
"_id": ObjectId("63ac1e59c0afb8b6f2d41acd"),
"fullname": "Jim Corbett",
"email": "jim.corbett#pp.com"
}
]
},
{
"productIndex": 2,
"productCategory": ObjectId('614g2f77bff86cd755439021'),
"productOwners": [
{
"_id": ObjectId("63ac1e59c0afb8b6f2d41acd"),
"fullname": "Jim Corbett",
"email": "jim.corbett#pp.com"
},
{
"_id": ObjectId("63ac1e59c0afb8b6f2d41ace"),
"fullname": "Carry Minatti",
"email": "carry.minatty#pp.com"
}
]
},
{
"productIndex": 3,
"productCategory": "",
"productOwners": ""
}
]
}
]
In the collectionA we are having other documents as well, its not just one document.
Similarly for collectionB we are having other documents too.
How we can get this desirable output?
I am expecting the mongodb query for getting this solution.
I have implemented the lookup like the following
db.collectionA.aggregate([
{
"$lookup": {
"from": "collectionB",
"localField": "products.productOwners",
"foreignField": "_id",
"as": "inventory_docs"
}
}
])
You can try this:
db.collectionA.aggregate([
{
"$unwind": "$products"
},
{
"$lookup": {
"from": "collectionB",
"localField": "products.productOwners",
"foreignField": "_id",
"as": "products.productOwners"
}
},
{
"$group": {
"_id": {
id: "$id",
uniqueRefId: "$uniqueRefId"
},
"products": {
"$push": "$products"
}
}
},
{
"$project": {
id: "$_id.id",
uniqueRefId: "$_id.uniqueRefId",
products: 1,
_id: 0
}
}
])
Playground link.
In this query, we do the following:
First we unwind the products array, using $unwind.
Then we calculate productOwners, using $lookup.
Then we group the unwinded elements, using $group.
Finally we, project the desired output using $project.

$addFields is not adding value in document

Query is as follows and result is given below:
What I want is I am adding field called name, in which I want categoryObj[0].categoryName but it is empty.
Tried categoryObj.$.categoryName but giving error.
Once name is obtained as I want i will exclude categoryObj with project opertator.
Thanks for help in advance
let itemsByCategory = await VendorItem.aggregate([
{$match: {vendor: vendorId}},
{$lookup: {
from: "vendorcategories",
localField: "category",
foreignField: "_id",
as: 'categoryDetails'
}},
{$group:{
"_id":"$category",
"count":{"$sum":1},
"items":{"$push":"$$ROOT"},
"categoryObj":{"$addToSet":"$categoryDetails"}
}},
{$project: {"items.categoryDetails":0}},
{$addFields: {"categoryName" : "$categoryObj.categoryName"}},
//{$project: {"categoryObj":0}},
]);
and the result is as follows
{
"itemsByCategory": [
{
"_id": "62296d612a1462a7d5e4b86b",
"count": 1,
"menuItems": [
{
"_id": "622971fa4fda7b4c792a7812",
"category": "62296d612a1462a7d5e4b86b",
"vendor": "62296c6f2a1462a7d5e4b863",
"item": "Dahi Chaat",
"price": 30,
"inStock": true,
"variants": [
{
"variantName": "With Sev",
"variantPrice": 40,
"_id": "622975b9f7bdf6c2a3b7703c"
}
],
"toppings": [
{
"name": "cheese",
"price": 10,
"inStock": true,
"_id": "62297766ff9f01d236c60736"
}
],
"categoryDetails": [
{
"_id": "62296d612a1462a7d5e4b86b",
"categoryName": "Snacks",
"categoryDescription": "Desciption changed!",
"vendor": "621c6c944d6d79e83219e59a",
"__v": 0
}
]
}
],
"categoryObj": [
[
{
"_id": "62296d612a1462a7d5e4b86b",
"categoryName": "Snacks",
"categoryDescription": "Desciption changed!",
"vendor": "621c6c944d6d79e83219e59a",
"__v": 0
}
]
],
"name": []
}
]
}
You can add an $unwind phase in order to "loop" all objects inside "categoryObj", but you will need to group it back afterwards:
{"$addFields": {orig_id: "$_id"}},
{"$unwind": "$categoryObj"},
{"$addFields": {"name": {"$arrayElemAt": ["$categoryObj", 0]}}},
{"$group": {_id: "$orig_id", name: {$push: "$name.categoryName"},
menuItems: {$first: "$menuItems"}, count: {$first: "count"},
}
}
See playground here:
https://mongoplayground.net/p/wsH2Y0UZ_FH

How can i optimize my query i have written to find the Users and there last order details using aggregate, it shows me timeout as the dataset is large

I have a query as below, what it does it creates a link between two documents and find the last order date and users details like email, phone, etc. but on large data set it shows me timeout error any help would be much appreciated, and thanks in advance for the help
db.users.aggregate([
{
"$lookup": {
"from": "orders",
"let": {
"id": "$_id"
},
"pipeline": [
{
"$addFields": {
"owner": {
"$toObjectId": "$owner"
}
}
},
{
"$match": {
$expr: {
$eq: [
"$owner",
"$$id"
]
}
}
},
],
"as": "orders"
}
},
{
"$unwind": {
path: "$orders",
preserveNullAndEmptyArrays: false,
includeArrayIndex: "arrayIndex"
}
},
{
"$group": {
"_id": "$_id",
"order": {
"$last": "$orders.createdAt"
},
"userInfo": {
"$mergeObjects": {
name: "$name",
email: "$email",
phone: "$phone",
orderCount: "$orderCount"
}
}
}
},
{
"$project": {
name: "$userInfo.name",
email: "$userInfo.email",
phone: "$userInfo.phone",
orderCount: "$userInfo.orderCount",
lastOrder: "$order",
}
}
]
)
my documents look like the following for orders
{
"_id": ObjectId("607fbeeb0a752a66a7af40eb"),
"address": {
"loc": [
-1,
3
],
"_id": "5d35d55d3d081f486d0d401c",
"apartment": "",
"description": "ACcdg dfef"
},
"approvedAt": ISODate("2021-04-21T11:28:05.295+05:30"),
"assignedAt": null,
"billingAddress": {
"description": ""
},
"createdAt": ISODate("2021-04-21T11:28:04.449+05:30"),
"creditCard": "",
"deliveryDate": "04/21/21",
"deliveryDateObj": ISODate("2021-04-21T12:27:58.746+05:30"),
"owner": "609bd5831b912947ea51a9ac",
"products": [
"5a070c079b"
],
"updatedAt": ISODate("2021-04-21T11:28:05.295+05:30"),
}
and for users, it is like below
{
"_id": ObjectId("609bd5831b912947ea51a9ac"),
"updatedAt": ISODate("2021-05-12T18:47:55.291+05:30"),
"createdAt": ISODate("2021-05-12T18:47:55.213+05:30"),
"email": "1012#gmail.com",
"phone": "123",
"dob": "1996-04-10",
"password": "",
"stripeID": "",
"__t": "Customer",
"name": {
"first": "A",
"last": "b"
},
"orderCount": 1,
"__v": 0,
"forgottenPassword": ""
}
convert _id to string in lookup's let and you can remove $addFields from lookup pipeline
add $project stage in lookup pipeline and show only required fields
$project to show required fields and get last / max createdAt date use $max, you don't need to $unwind and $group operation
db.users.aggregate([
{
$lookup: {
from: "orders",
let: { id: { $toString: "$_id" } },
pipeline: [
{ $match: { $expr: { $eq: ["$owner", "$$id"] } } },
{
$project: {
_id: 0,
createdAt: 1
}
}
],
"as": "orders"
}
},
{
$project: {
email: 1,
name: 1,
orderCount: { $size: "$orders" },
phone: 1,
lastOrder: { $max: "$orders.createdAt" }
}
}
])
Playground
SUGGESTION:
You can save owner id in orders as objectId instead of string and whenever new order arrive store it as objectId, you can prevent conversation operator $toString operation
create an index in owner field to make lookup process faster.
I have figured out that after using createIndex for the owner field which is used to compare the owner in the orders from the users _id filed, so just after adding an db.orders.createIndex({ owner: 1 }), the query will run much faster and smoother

How to Pull Out nested Objects in Mongodb?

I'm trying to make a query by merging from another collection, but there are obstacles when the query is run, the data generated is not what I imagined
i have the data like this
{
"_id": "5ce8981a46039c14a4ec32d1",
"name": "Monkey D Luffy",
"email": "aaa#aaa.com",
"status": "not verified",
"password": "$2a$10$ayluBIsOOelBTIk.69GjHubgQemr6dJfgBUELNusCOaUGLpS/qKs6",
"metas": {
"role": "admin",
"smartphone": "ios",
"address": "konoha",
"hobby": "eat ramen"
}
},
and i want pull out metas from nested document :
{
"_id": "5ce8981a46039c14a4ec32d1",
"name": "Monkey D Luffy",
"email": "aaa#aaa.com",
"status": "not verified",
"password": "$2a$10$ayluBIsOOelBTIk.69GjHubgQemr6dJfgBUELNusCOaUGLpS/qKs6",
"role": "admin",
"smartphone": "ios",
"address": "konoha",
"hobby": "eat ramen"
},
if any duplicate from my question pls suggest me, because I didn't find the same question, mostly using arrays.
and here is my query:
db.accounts.aggregate([
{
$lookup: {
from: "account_meta",
localField: "_id",
foreignField: "account_id",
as: "metas"
}
},
{ "$unwind": "$metas" },
{
$group: {
_id: "$_id",
name: {$first:"$name"},
status: {$first: "$status"},
email: {$first: "$email"},
password: {$first: "$password"},
data: {
"$push": {
"k" : "$metas.key",
"v": "$metas.value"
}
}
}
},
{
$project: {
"_id": "$_id",
"name": "$name",
"email": "$email",
"status": "$status",
"password": "$password",
"metas" :{
$arrayToObject: "$data"
}
}
},
{
"$replaceRoot": {
"newRoot":
{
"$mergeObjects": [ {$arrayToObject: "$data"}, "$$ROOT"]
},
}
},
])
i just edit some code from my $mergeObject:
{
"$replaceRoot": {
"newRoot":
{
"$mergeObjects": [ "$metas", "$$ROOT"]
},
}
},
{$project: { metas: 0} }

MongoDB multiple counts, single document, arrays

I have been searching on stackoverflow and cannot find exactly what I am looking for and hope someone can help. I want to submit a single query, get multiple counts back, for a single document, based on array of that document.
My data:
db.myCollection.InsertOne({
"_id": "1",
"age": 30,
"items": [
{
"id": "1",
"isSuccessful": true,
"name": null
},{
"id": "2",
"isSuccessful": true,
"name": null
},{
"id": "3",
"isSuccessful": true,
"name": "Bob"
},{
"id": "4",
"isSuccessful": null,
"name": "Todd"
}
]
});
db.myCollection.InsertOne({
"_id": "2",
"age": 22,
"items": [
{
"id": "6",
"isSuccessful": true,
"name": "Jeff"
}
]
});
What I need back is the document and the counts associated to the items array for said document. In this example where the document _id = "1":
{
"_id": "1",
"age": 30,
{
"totalIsSuccessful" : 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1,
"totalNameNull": 2
}
}
I have found that I can get this in 4 queries using something like this below, but I would really like it to be one query.
db.test1.aggregate([
{ $match : { _id : "1" } },
{ "$project": {
"total": {
"$size": {
"$filter": {
"input": "$items",
"cond": { "$eq": [ "$$this.isSuccessful", true ] }
}
}
}
}}
])
Thanks in advance.
I am assuming your expected result is invalid since you have an object literal in the middle of another object and also you have totalIsSuccessful for id:1 as 2 where it seems they should be 3. With that said ...
you can get similar output via $unwind and then grouping with $sum and $cond:
db.collection.aggregate([
{ $match: { _id: "1" } },
{ $unwind: "$items" },
{ $group: {
_id: "_id",
age: { $first: "$age" },
totalIsSuccessful: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalNotIsSuccessful: { $sum: { $cond: [{ "$ne": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalSuccessfulNull: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", null ] }, 1, 0 ] } },
totalNameNull: { $sum: { $cond: [ { "$eq": [ "$items.name", null ]}, 1, 0] } } }
}
])
The output would be this:
[
{
"_id": "_id",
"age": 30,
"totalIsSuccessful": 3,
"totalNameNull": 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1
}
]
You can see it working here