mongodb rename keys inside an object during aggregation - mongodb

I have a mongodb aggregation query
return this.collection.aggregate([
{ $match: { _id: ObjectID(locationId) } },
{
$lookup:{
from: "buildings",
localField: "userId",
foreignField: "ownerId",
as: "areas"
}
},
{
$project: {
"location_id": "$_id",
"locationName": "$locationName",
"areas": "$areas"
}
}
]).toArray();
where the areas field is an array of objects
[{
key: value
}]
Is it possible to rename this key during the aggregation process?

Yes, though maybe not as easily as you might expect.
First you need to use $unwind to get a document per area. Than $group the documents with the same location (thus same _id) back together.
The $group actually also does a projection. You can use $first in $group to select fields that ware not unwinded.
You haven't told the properties of an area object. In the example I assume it has a coords and shape property.
return this.collection.aggregate([
{ $match: { _id: ObjectID(locationId) } },
{
$lookup:{
from: "buildings",
localField: "userId",
foreignField: "ownerId",
as: "areas"
}
},
{
$unwind: "$areas"
},
{
$group: {
"_id": "$_id",
"location_id": { $first: "$_id" },
"locationName": { $first: "$locationName" },
"areas": {
$push: {
"latitude": "$coords.lat",
"longitude": "$coords.lon",
"shape": "$shape"
}
}
}
}
]).toArray();
Note that you do end up with an additional _id field (which may be ignored), because $group requires an _id.

Yes, you can unwind - group your areas as following:
return this.collection.aggregate([
{ $match: { _id: ObjectID(locationId) } },
{
$lookup:
{
from: "buildings",
localField: "userId",
foreignField: "ownerId",
as: "areas"
}
},
{
$project: { "location_id": "$_id",
"locationName": "$locationName",
"areas": "$areas"
}
},
{
$unwind: "$areas"
},
{
$group: { "_id": {"location_id": "$_id", "locationName": "$locationName"},
"areas": {$push: {new_key: "$areas.key"}} //<== renaming 'key' to 'new_key'
}
},
{
$project: { "_id": 0,
"location_id": "$_id.location_id",
"locationName": "$_id.locationName",
"areas": "$areas"
}
}
]).toArray();

Related

Change element name from the result set of Mongo DB Query

I have collection like below named as "FormData",
{
"_id": ObjectId("5e3c27bf1ef77236945ef07b"),
"eed12747-0923-4290-b09c-5a05107f5609": "20200206",
"bd637691-782d-4cfd-8624-feeedfe11b3e": "20200206_1#mail.com"
}
I have another collection named as "Form" which will have Title of Fields,
{
"_id": ObjectId("5e3c27bf1ef77236945ef07b"),
"Fields":[
{
"FieldID": "eed12747-0923-4290-b09c-5a05107f5609",
"Title": "Phone"
},
{
"FieldID": "bd637691-782d-4cfd-8624-feeedfe11b3e",
"Title": "Email"
}]
}
Now I have to map element name with Form field title and I need result like below,
{
"_id": ObjectId("5e3c27bf1ef77236945ef07b"),
"Phone": "20200206",
"Email": "20200206_1#mail.com"
}
Please help me to solve this.
Thanks in advance!
You can:
$objectToArray to convert the $$ROOT document into an array of k-v pairs for future lookups
use a sub-pipeline in $lookup to find the value by the uuid
use $mergeObject to combine the original values(i.e. "20200206"...) with the new field name looked up (i.e. "Phone"...)
wrangle the result back into original form using $arrayToObject and $replaceRoot
db.FormData.aggregate([
{
$match: {
"_id": ObjectId("5e3c27bf1ef77236945ef07b")
}
},
{
$project: {
kv: {
"$objectToArray": "$$ROOT"
}
}
},
{
$unwind: "$kv"
},
{
"$lookup": {
"from": "Form",
"let": {
uuid: "$kv.k"
},
"pipeline": [
{
$match: {
"_id": ObjectId("5e3c27bf1ef77236945ef07b")
}
},
{
"$unwind": "$Fields"
},
{
$match: {
$expr: {
$eq: [
"$$uuid",
"$Fields.FieldID"
]
}
}
},
{
$project: {
_id: false,
k: "$Fields.Title"
}
}
],
"as": "formLookup"
}
},
{
$unwind: "$formLookup"
},
{
$project: {
kv: {
"$mergeObjects": [
"$kv",
"$formLookup"
]
}
}
},
{
$group: {
_id: "$_id",
kv: {
$push: "$kv"
}
}
},
{
"$project": {
newDoc: {
"$arrayToObject": "$kv"
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{
"_id": "$_id"
},
"$newDoc"
]
}
}
}
])
Mongo Playground
Another option is to start from Form collection and avoid $unwind:
$match and $lookup to get all needed data into one document
$objectToArray to get known keys for FormData
Match the items using $indexOfArray and $arrayElemAt and merge them using $mergeObjects. Then use arrayToObject to format the response
db.Form.aggregate([
{$match: {_id: ObjectId("5e3c27bf1ef77236945ef07b")}},
{$lookup: {
from: "FormData",
localField: "_id",
foreignField: "_id",
as: "formLookup",
pipeline: [{$project: {_id: 0}}]
}},
{$set: {formLookup: {$objectToArray: {$first: "$formLookup"}}}},
{$replaceRoot: {
newRoot: {
$mergeObjects: [
{$arrayToObject: {
$map: {
input: "$formLookup",
in: {$mergeObjects: [
{v: "$$this.v"},
{k: {$getField: {
input: {$arrayElemAt: [
"$Fields",
{$indexOfArray: ["$Fields.FieldID", "$$this.k"]}
]},
field: "Title"
}}}
]}
}
}},
{_id: "$_id"}
]
}
}}
])
See how it works on the playground example

MongoDB aggregate $lookup with _ID's from array

I have two collections in MongoDB: "carts" and another "products"
Carts:
[{
"_id": {
"$oid": "62af0fefebc0b42a875c7df1"
},
"uuid": "6ca05ae0-522a-4db3-b380-2d2330ee1e27",
"cartitems": [
"62a0b24680cc2891148daf7b",
"62a7339d91d01868921afa0a",
"62a72f7191d01868921afa08",
"62a7330291d01868921afa09"
],
"created": "2022-06-19T14:00:47.846958537+02:00[Europe/Brussels]",
"lastupdate": "2022-06-19T14:01:06.15165564+02:00[Europe/Brussels]"
},
{...},
{...}]
products:
[{
"_id": {
"$oid": "62a0b24680cc2891148daf7b"
},
"name": "Product1",
"created": "2022-06-11T09:41:54.461308647+02:00[Europe/Brussels]",
"category": "Workshops",
"pricein": "28900",
"lastupdate": "2022-06-17T16:09:53.385655474+02:00[Europe/Brussels]"
},
{...},
{...}]
I would like to use Aggregate:
db.carts.aggregate([
{ $match: { uuid: "6ca05ae0-522a-4db3-b380-2d2330ee1e27" } },
{
$lookup: {
from: "products",
localField: "cartitems",
foreignField: "_id",
as: "output"
}
}
])
This is not working because localField: "cartitems" should be converted:
"$addFields": {
"convertedIdStr": {
"$toString": "$cartitems"
}
But I don't manage to convert because cartitems is an array.
Any help would be great, Thanks a lot!
Use $lookup with pipeline. In $lookup pipeline stage, add $match stage by filtering the (converted to string) product's _id is in cartitems (array) variable.
db.carts.aggregate([
{
$match: {
uuid: "6ca05ae0-522a-4db3-b380-2d2330ee1e27"
}
},
{
$lookup: {
from: "products",
let: {
cartitems: "$cartitems"
},
pipeline: [
{
$match: {
$expr: {
$in: [
{
$toString: "$_id"
},
"$$cartitems"
]
}
}
}
],
as: "output"
}
}
])
Sample Mongo Playground

Adding remaining filed in $project after adding lookups in mongoose

I'm doing a $lookup from an _id in Order schema, and its working as expected. But in $project how to add remaining keys. I have added my code below.
Product Collection:
{
"_id": "54759eb3c090d83494e2d804",
"product_name": "sample product",
"image": "default.png",
"price": 55,
"discount": 5,
}
Order list Collection
{
"user_name": "sample1",
"product_list":[
{
"product_id": "54759eb3c090d83494e2d804"
"quantity": 5
}
]
}
lookups
[
{
from: 'product',
localField: 'product_list.product_id',
foreignField: '_id',
as: 'product_list.product_id',
model: 'ProductModel',
},
],
$Project
{
user_name: true,
product_list: {
$map: {
input: '$product_list.product_id',
as: 'product',
in: {
product_name: '$$product.product_name',
},
},
},
}
Current Result:
{
"user_name": "sample1",
"product_list":[
"product_id":{
"product_name": "sample product"
}
]
}
In this current result, the quantity field is missing. How to add in $project?. The expected result shown below
Expected Result:
{
"user_name": "sample1",
"product_list":[
{
"product_id": {
"product_name": "sample product"
}
"quantity": 5
}
]
}
You need to do $unwind before $lookup, because it will not work directly in array fields, and here you don't need $map inside $project,
$unwind product_list deconstruct array
db.order.aggregate([
{ $unwind: "$product_list" },
$lookup with pipeline, this will allow to use pipeline inside lookup, here $project to required fields
{
$lookup: {
from: "product",
as: "product_list.product_id",
let: { product_id: "$product_list.product_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$$product_id", "$_id"] }
}
},
{
$project: {
_id: 0,
product_name: 1
}
}
]
}
},
$unwind with path product_list.product_id because you need it as object
{ $unwind: { path: "$product_list.product_id" } },
$group by _id re-construct your product_list array
{
$group: {
_id: "$_id",
user_name: { $first: "$user_name" },
product_list: { $push: "$product_list" }
}
}
])
Playground

How to perform lookup in aggregation when foreignField is in array?

I have two collections:
// users
{
_id: "5cc7c8773861275845167f7a",
name: "John",
accounts: [
{
"_id": "5cc7c8773861275845167f76",
"name": "Name1",
},
{
"_id": "5cc7c8773861275845167f77",
"name": "Name2",
}
]
}
// transactions
{
"_id": "5cc7c8773861275845167f75",
"_account": "5cc7c8773861275845167f76",
}
Using lookup I want to populate _account field in transactions collection with respective element from users.accounts array.
So, I want the final result as:
{
"_id": "5cc7c8773861275845167f75",
"_account": {
"_id": "5cc7c8773861275845167f76",
"name": "Name1",
},
}
I have already tried using this code:
db.transactions.aggregate([
{
$lookup:
{
from: "users.accounts",
localField: "_account",
foreignField: "_id",
as: "account"
}
}
])
In the result account array comes as empty.
What is the correct way to do it ?
You can use below aggregation with mongodb 3.6 and above
db.transactions.aggregate([
{ "$lookup": {
"from": "users",
"let": { "account": "$_account" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$$account", "$accounts._id"] } } },
{ "$unwind": "$accounts" },
{ "$match": { "$expr": { "$eq": ["$$account", "$accounts._id"] } } }
],
"as": "_account"
}},
{ '$unwind': '$_account' }
])
Try with this
I think case 1 is better.
1)-
db.getCollection('transactions').aggregate([
{
$lookup:{
from:"user",
localField:"_account",
foreignField:"accounts._id",
as:"trans"
}
},
{
$unwind:{
path:"$trans",
preserveNullAndEmptyArrays:true
}
},
{
$unwind:{
path:"$trans.accounts",
preserveNullAndEmptyArrays:true
}
},
{$match: {$expr: {$eq: ["$trans.accounts._id", "$_account"]}}},
{$project:{
_id:"$_id",
_account:"$trans.accounts"
}}
])
2)-
db.getCollection('users').aggregate([
{
$unwind:{
path:"$accounts",
preserveNullAndEmptyArrays:true
}
},
{
$lookup:
{
from: "transactions",
localField: "accounts._id",
foreignField: "_account",
as: "trans"
}
},
{$unwind:"$trans"},
{
$project:{
_id:"$trans._id",
_account:"$accounts"
}
}
])

MongoDB: Sum of all items in referenced collection

I've got some issues getting the balance of all my accounts. All accounts have references to different deposit items in deposits collection.
Accounts collection:
[{
"_id": "56b1ce63315748b44f1174e1",
"name": "Foo bar",
"deposits": [
{
"$oid": "56b1ce78315748b44f1174e2"
}
]
}]
Deposits collection:
{
"_id": {
"$oid": "56b1deb84f40bfa435e22f3f"
},
"account": {
"$oid": "56b1dea34f40bfa435e22f3e"
},
"amount": 300,
"date": {
"$date": "2016-02-01T00:00:00.000Z"
}
}
I've tried to aggregate the query but it always returns balance: 0. I guess I need to populate the item before the use of aggregate. But how do I do that?
Accounts.aggregate([
{ $unwind: "$deposits" },
{
$group: {
_id: "$_id",
name: { "$first": "$name" },
balance: { $sum: "$deposits.amount" }
}
}])
Solution:
{
$lookup:
{
from: 'deposits',
localField: 'amount',
foreignField: 'deposits',
as: 'deposits'
},
},
{ $unwind: "$deposits" },
{
$group: {
id: "$_id",
name: { "$first": "$name" },
balance: { $sum: "$deposits.amount" }
}
}
You need to use the $lookup operator in order to join the two collections, your current approach doesn't work. The $lookup operator is only available in Mongo 3.2 and higher.
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}