How to Pull Out nested Objects in Mongodb? - 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} }

Related

Display only select nested fields of object in MongoDB Compass aggregation

I have the following data model:
{
"_id": {
"$oid": "63b6da81661f0ecd23cd9830"
},
"Plan": [
{
"_id": {
"$oid": "63b6311e0871625f7ceb85ad"
},
"Name": "Straight ankle lock",
"Date": {
"$date": {
"$numberLong": "1672725600000"
}
},
"Notes": "Christian taught ankle locks",
"TeamId": {
"$oid": "63a291ebb60592854e23b8fb"
}
}
],
"User": [
{
"_id": {
"$oid": "6240fd2ee1335b45680bee9d"
},
"FirstName": "Test",
"LastName": "User",
"TeamId": {
"$oid": "639fd03bb31c7995a9d4b28c"
}
}
]
}
And I'd like to show a new object via aggregation that looks like:
{
"_id": {
"$oid": "63b6da81661f0ecd23cd9830"
},
"PlanName": "Straight ankle lock",
"UserName": "Test User"
}
I've been trying to figure this out for a few days, but at this point not sure if it is even possible. Any ideas?
Thanks.
Newer model based on Ray's input using project:
{
"_id": {
"$oid": "63b6da81661f0ecd23cd9830"
},
"InsertDate": {
"$date": {
"$numberLong": "1672927873507"
}
},
"Plan": {
"Name": "Straight ankle lock"
},
"User": {
"FirstName": "Adam",
"LastName": "Gusky"
},
"Team": {
"TeamName": "GB2 No Gi"
}
}
The query I'm using to get the above data:
[
{
$lookup: {
from: "Plans",
localField: "PlanId",
foreignField: "_id",
as: "Plan",
},
},
{
$lookup: {
from: "Teams",
localField: "TeamId",
foreignField: "_id",
as: "Team",
},
},
{
$lookup: {
from: "Users",
localField: "UserId",
foreignField: "_id",
as: "User",
},
},
{
$project: {
Plan: {
$first: "$Plan",
},
User: {
$first: "$User",
},
Team: {
$first: "$Team",
},
InsertDate: 1,
},
},
{
$project: {
"Plan.Name": 1,
"User.FirstName": 1,
"User.LastName": 1,
"Team.TeamName": 1,
InsertDate: 1,
},
},
]
You can simply set the value you want in the $project stage.
db.collection.aggregate([
{
$project: {
_id: 1,
PlanName: {
$first: "$Plan.Name"
},
UserName: {
"$concat": [
{
"$first": "$User.FirstName"
},
" ",
{
"$first": "$User.LastName"
}
]
}
}
}
])
Mongo Playground

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 transform data using aggregate function 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

MongoDb documents join with inner objects

I have 2 documents named calls and customers
i want to join locationId of calls to customers.location._id
calls
"_id": "5d456560221b9147e4af4b97",
"customerId": "5d0a3bbfac9470aea56ad619",
"locationId": "5d11d2c4cba8151f67f735b1",
"startDateTime": "2019-06-21T05:30:00.000Z",
"endDateTime": "2019-06-21T06:00:00.000Z"
}
customers
"_id": "5d0a3bbfac9470aea56ad619",
"locations": [
{
"_id": "5d11d2c4cba8151f67f735b1",
"phone": "9889599999",
"ext": "91",
"address": "sad",
"neighborhood": "sda",
"city": "punetest",
"state": "MH",
"zip": "589365",
"country": "india",
"isBillingAddress": false,
"taxPercentage": 0,
"type": "Residential",
"status": "Active",
"firstName": "A",
"lastName": "B"
},
{
"_id": "5d11e457cba8151f67f735b3",
"phone": "969696999",
"ext": "91",
"address": "",
"neighborhood": "",
"city": "udaipur",
"state": "raj",
"zip": "312563",
"country": "india",
"isBillingAddress": false,
"taxPercentage": 0,
"type": "Residential",
"status": "Deleted",
"firstName": "AB",
"lastName": "CD"
}
],
"createdAt": "2019-06-19T13:42:23.479Z",
"updatedAt": "2019-06-25T13:39:07.597Z"
}
[
{
$lookup: {
from: 'customers.locations',
localField: 'locationId',
foreignField: '_id',
as: 'customers.locations',
},
}
]);
it is not working
I have 2 documents named calls and customers i want to join locationId of calls to customers.location._id
I want output```
{
"_id": "5d456560221b9147e4af4b97",
"customerId": "5d0a3bbfac9470aea56ad619",
"locationId": "5d11d2c4cba8151f67f735b1",
"location":{"_id": "5d11d2c4cba8151f67f735b1",
"phone": "9889599999",
"ext": "91",
"address": "sad",
"neighborhood": "sda",
"city": "punetest",
"state": "MH",
"zip": "589365",
"country": "india",
"isBillingAddress": false,
"taxPercentage": 0,
"type": "Residential",
"status": "Active",
"firstName": "A",
"lastName": "B"}
}
Ok, this is what you're looking at :
If your locationId in calls has only one matching customer.locations._id in customer :
db.getCollection('calls').aggregate([
{
$lookup: {
from: 'customers',
localField: 'locationId',
foreignField: 'locations._id',
as: 'customersCalls',
}
}, { $unwind: '$customersCalls' },
{
$project: {
customerId: 1, locationId: 1, locations: {
$filter: {
input: "$customersCalls.locations",
as: "item",
cond: { $eq: ["$$item._id", '$locationId'] }
}
}
}
},
{ $group: { _id: '$_id', result: { $first: '$$ROOT' } } },
{ $replaceRoot: { newRoot: "$result" } }
])
else if your locationId in calls has multiple matching customer.locations._id in customer :
db.getCollection('calls').aggregate([
{
$lookup: {
from: 'customers',
localField: 'locationId',
foreignField: 'locations._id',
as: 'customersCalls',
}
}, { $unwind: '$customersCalls' },
{
$project: {
customerId: 1, locationId: 1, locations: {
$filter: {
input: "$customersCalls.locations",
as: "item",
cond: { $eq: ["$$item._id", '$locationId'] }
}
}
}
},
{ $group: { _id: '$_id', locations: { $push: { $arrayElemAt: ["$locations", 0] } }, result: { $first: '$$ROOT' } } },
{ $addFields: { 'result.locations': '$locations' } },
{ $replaceRoot: { newRoot: "$result" } }
])

Mongo DB aggrregation on multiple arrays

I want to retrieve from my cart items and bundles that are not deleted.
my cart looks like this:
{
"_id": "589474849d7b3f439797faf1",
"bundles": [{
"id": "57c98e25298cd0f908021c12",
"serial": "xxxx",
"status": ""
}],
"items": [{
"id": "589de9a690d632ccbc10cd64",
"status": "deleted",
"quantity": 1,
"serial": "fffff"
}]
}
What I tried was:
[
{$match: condition},
{$unwind: {"path": "$items", "preserveNullAndEmptyArrays": true}},
{$unwind: {"path": "$bundles", "preserveNullAndEmptyArrays": true}},
{$match: {"items.status": {$ne: "deleted"}}},
{$match: {"bundles.status": {$ne: "deleted"}}},
{
"$group": {
"_id": "$_id",
currency: {$first: "$currency"},
tenant: {$first: "$tenant"},
user: {$first: "$user"},
"items": {"$addToSet": "$items"},
"bundles": {"$addToSet": "$bundles"}
}
}
];
It works fine for all cases except when there is only one deleted item, and multiple bundles. The query return no bundles at all
expected output:
{
"_id": "589474849d7b3f439797faf8",
"items": [{
"id": "589de9a690d632ccbc10cd64",
"quantity": 1,
"serial": "fff"
}, {
"id": "589de9a690d632ccbc10c55",
"quantity": 1,
"serial": "xxx"
}],
"bundles": [{
"id": "57c98e25298cd0f908021c12",
"serial": "pppp"
}]
}
Oh thanks guys I found the solution, filter will do the trick
[{
$match: "condition"
}, {
$project: {
items: {
$filter: {
input: "$items",
as: "item",
cond: {
$ne: ["$$item.status", "deleted"]
}
}
},
bundles: {
$filter: {
input: "$bundles",
as: "bundle",
cond: {
$ne: ["$$bundle.status", "deleted"]
}
}
}
}
}]