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
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
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
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
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"
}
}
])
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>
}
}