MongoDB Aggregegation: Distinct on other collection - mongodb

I have two collections:
employees
_id
office
jobTitle
offices
_id
city
I am trying to receive a list of all office locations with the job titles of employees of the respecitve office.
The end result would look like this:
[{
_id: ObjectId('6086f617cc0824cc4ce7c9f0'),
city: "Berlin",
jobTitles: ['SOFTWARE ENGINEER', 'CEO', 'CFO']
}, {
_id: ObjectId('60c08d36f925f3083488ea79'),
city: "Prague",
jobTitles: ['UX DESIGNER', 'BUSINESS ANALYST']
}]
This is the aggregation I've tried, with no success:
db.offices.aggregate([{
$lookup: {
from: 'employees',
localField: '_id',
foreignField: 'office',
as: 'jobTitles',
project: [{
$group: { _id: '$jobTitle'}
}]
}
}]);
One office can have thousands of employees, so I'm trying to make the query as efficient as possible.
Thank you for your ideas! :)

use $lookup
db.offices.aggregate([
{
"$lookup": {
"from": "employees",
"localField": "_id",
"foreignField": "office",
"as": "jobTitles"
}
},
{
"$set": {
"jobTitles": "$jobTitles.jobTitles"
}
},
{
"$unwind": "$jobTitles"
},
{
"$group": {
"_id": "$_id",
"city": {
"$first": "$city"
},
jobTitles: {
$addToSet: "$jobTitles"
}
}
}
])
mongoplayground

Related

How to do an aggregation and lookup on double nested array in MongoDB?

I have three collections named issue, category and article, they are defined as follows:
// issue
{
"_id": "612775fbd237d9769a9fc3e4",
"title": "Weekly Issue1",
"data": [
{
"categoryId": ObjectId("61272e7dd237d9769a9fc3d9"),
"articles": [ ObjectId("61272f29d237d9769a9fc3da"), ...]
},
...
]
}
]
}
// category
{
"_id" : "61272e7dd237d9769a9fc3d9",
"name" : "News"
}
// article
{
"_id" : "61272f29d237d9769a9fc3da",
"title" : "Some News",
"url" : "https://www.google.com"
}
I would like to do an aggregation on issue and hope to get JSON results like this:
{
"id": "612775fbd237d9769a9fc3e4",
"tittle": "Weekly Issue1",
"data": [
{
"categoryId": "61272e7dd237d9769a9fc3d9",
"categoryName": "News"
"articles": [ {id: "61272f29d237d9769a9fc3da", title:"Some News", url:"https://www.google.com"}, ...]
},
...
]
}
]
}
My question is how to write the aggregation script? I am just a newbie to MongoDB, I have no idea about it now.
db.issue.aggregate([])
Thanks :-)
$unwind deconstruct data array
$lookup with article collection
$lookup with category collection
$addFields to edit categoryName field, $arrayElemAt to get first element from result of category
$group by _id and reconstruct the data array and return required fields by $first operator
db.issue.aggregate([
{ $unwind: "$data" },
{
$lookup: {
from: "article", // replace your original collection name
localField: "data.articles",
foreignField: "_id",
as: "data.articles"
}
},
{
$lookup: {
from: "category", // replace your original collection name
localField: "data.categoryId",
foreignField: "_id",
as: "data.categoryName"
}
},
{
$addFields: {
"data.categoryName": { $arrayElemAt: ["$data.categoryName.name", 0] }
}
},
{
$group: {
_id: "$_id",
title: { $first: "$title" },
data: { $push: "$data" }
}
}
])
Playground
use this aggregate
I think you could use $project instead of $addField but in test, I got some issue, and use addFields
[
{
'$lookup': {
'from': 'category',
'localField': 'data.0.categoryId',
'foreignField': '_id',
'as': 'dataCategory'
}
}, {
'$lookup': {
'from': 'article',
'localField': 'data.0.articles',
'foreignField': '_id',
'as': 'dataArticles'
}
}, {
'$unwind': {
'path': '$dataCategory'
}
}, {
'$addFields': {
'data': [
{
'categoryId': '$dataCategory._id',
'categoryName': '$dataCategory.name',
'articles': '$dataArticles'
}
]
}
}, {
'$project': {
'data': 1,
'tittle': 1
}
}
]
Because you have a nested array, you can do also a nested $lookup
with the first $lookup we add the category information
with the second nested $lookup we add the articles info
(the middle $addFields is to keep from the joined-category only its own articles)
Test code here
db.issue.aggregate([
{
"$lookup": {
"from": "category",
"let": {"data": "$data"},
"pipeline": [
{"$match": {"$expr": {"$in": ["$_id","$$data.categoryId"]}}},
{
"$addFields": {
"articles": {
"$arrayElemAt": [
{
"$filter": {
"input": "$$data",
"as": "d",
"cond": {"$eq": ["$$d.categoryId","$_id"]
}
}
},0]}}
},
{
"$lookup": {
"from": "article",
"localField": "articles.articles",
"foreignField": "_id",
"as": "articles"
}
}
],
"as": "data"
}
}
])

MongoDB Lookup Aggregation

I have Parking collection like:
parking: {
_id: xxxxxx,
name: xxxxxx,
vehicles: [{vehicleId: xxxxx, bid: xxxxx}...]
}
and car collection:
car: {
_id: "xxxx",
attributes: [xxxxx],
color: "xxxx"
}
When I do Lookup Aggregation:
$lookup: {
from: "car",
localField: "vehicles.vehicleId",
foreignField: "_id",
as: "cars"
}
I get:
parking: {
_id: xxxxxx,
name: xxxxxx,
vehicles: [{vehicleId: xxxxx, bid: xxxxx}],
cars: [car1,car2...]
}
So I struggle with merging new cars array with objects in vehicles array that match id.
Can I somehow replace vehicleId with car document that match?
I tried this but group operation remove name field from parking
db.parking.aggregate([
{ "$unwind": "$vehicles" },
{ "$lookup": {
"from": "car",
"as": "vehicles.vehicle",
"localField": "vehicles.vehicleId",
"foreignField": "_id"
}},
{ "$unwind": "$vehicles.vehicle" },
{ "$group": {
"_id": "$_id",
"vehicles": { "$push": "$vehicles" }
}}
])
It's easier use $map operator by combining the $reduce operator.
Try this one:
db.parking.aggregate([
{
"$lookup": {
"from": "car",
"localField": "vehicles.vehicleId",
"foreignField": "_id",
"as": "cars"
}
},
{
$addFields: {
vehicles: {
$map: {
input: "$vehicles",
as: "veh",
in: {
bid: "$$veh.bid",
vehicleId: {
$reduce: {
input: "$cars",
initialValue: "$$veh.vehicleId",
in: {
$cond: [
{
$eq: [ "$$this._id", "$$veh.vehicleId" ]
},
"$$this",
"$$value"
]
}
}
}
}
}
},
cars: "$$REMOVE"
}
}
])
MongoPlayground | Replace vehicleId

Using nested properties in $lookup

Let's assume I have a Person object. Each Person has n number of houses and each house has m number of rooms in it. In each room there is a special item id and I also have a collection of these items. So my Person object looks kinda like this:
person: {
name: "Fictional Name",
houses: [
{
rooms: [
{
itemId : "q23434c23"
},{
itemId : "q34c356b5"
},{...}
]
},{...}
]
}
Now when i aggregate my person collection i try to $lookup all these itemId's inside of it and store them as a variable. The problem is I don't know how to look them up. I tried running code below, but it works only for the first House:
db.persons.aggregate([
{
$lookup: {
from: "items",
localField: "houses.rooms.itemId",
foreignField: "_id",
as: "myItems"
}
},{
$project: {
name: 1,
myItems: "$myItems"
}
}
])
Edit: item looks kinda like this:
item: {
_id: "q23434c23",
type: "PLASTIC",
description: "basic description"
}
You can use below aggregation
db.persons.aggregate([
{ "$unwind": "$houses" }
{ "$lookup": {
"from": "items",
"localField": "houses.rooms.itemId",
"foreignField": "_id",
"as": "houses.rooms"
}},
{ "$group": {
"_id": "$_id",
"houses": { "$push": "$houses" }
"name": { "$first": "$name" }
}}
])

Mongodb: Not expected result from aggregate ($lookup)

I´m looking for some help.
I have a collection called 'business' where I have objects like this:
[{
"_id": ObjectId("5aefa97166763d28fc984c7a"),
"name": "Business 1",
"boxes": [
{
"_id": ObjectId("5ac6bb69f3c36e17f0d34bd2")
}
]
}]
There is another collection called boxes which has objects like:
[{
_id: ObjectId("5ac6bb69f3c36e17f0d34bd2"),
name:"Box1",
color:"blue"
}]
The idea here is that there are businesses that own boxes and I want to keep both collection separated.
That said, I would like to retrieve this result:
[{
"_id": ObjectId("5aefa97166763d28fc984c7a"),
"name": "Business 1",
"boxes": [{
_id: ObjectId("5ac6bb69f3c36e17f0d34bd2"),
name:"Box1",
color:"blue"
}]
}]
But I am getting this result:
[{
"_id": ObjectId("5aefa97166763d28fc984c7a"),
"name": "Business 1",
"boxes": [
{
"_id": ObjectId("5ac6bb69f3c36e17f0d34bd2")
}
]
}]
Using $lookup as you can see below:
db.db('database').collection("business").aggregate({
$lookup:{
from: "boxes",
localField: "_id",
foreignField: "_id",
as: "box"
},
"$unwind": "$boxes"
}).toArray(function(err, result) {
if (err) throw err;
res.send(result);
db.close();
res.end();
});
What am I doing wrong?
Thank you all!
This should help
db.business.aggregate([{$lookup:{from: "boxes", localField: "boxes._id", foreignField: "_id", as: "box" }},{"$project":{"_id":1,"name":1,"boxes":"$box"}}])
The lookup creates an array "box" which has all matching documents from the boxes collection.The next stage in the pipeline, $project , selects _id and name from the new document and renames the box array to boxes.
If You have multiple objectIds and need in one array Boxes:
Ex: "boxes": [
{
"_id": ObjectId("5ac6bb69f3c36e17f0d34bd2")
},
{
"_id": ObjectId("5ac6bb69f3c36e17f0d34bd3")
}
]
Then u need to do group
Query :
db.getCollection('business').aggregate([
{"$unwind": "$boxes"} ,
{$lookup:{
from: "boxes",
localField: "boxes._id",
foreignField: "_id",
as: "box"
}},
{
$group: {
_id: '$_id',
name: { $first: '$name' },
boxes: {
$push: {
_id: '$boxes._id',
name: '$boxes.name'
color: '$boxes.color'
}
}
}
}
])

mongodb rename keys inside an object during aggregation

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();