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" }
}}
])
Related
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
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"
}
}
])
I am doing an aggregation in Paper collection like below
const papers = await Paper.aggregate([
{
"$lookup": {
"from": "reviews",
"localField": "reviewId",
"foreignField": "_id",
"as": "review"
}
},
{ $unwind: '$review' }
]);
It returns the result that contains review object which has a reviews array like:
[
{
...
review: {
_id: 5f1638770f3a8d20f8c1beeb,
reviews: [Array],
},
...
}
]
If I make the review more clear, it is like below:
{
_id: 5f1638770f3a8d20f8c1beeb
reviews: [
{
_id: 5f164395857bcdd1d8674b69,
reviewerId: 5f15b28d534b5886c0d9eb8a
},
{
_id: 5f164395857bcdd1d8674b6a,
reviewerId: 5f1358c523dc2367c43a6311
}
]
}
In above, reviewerId inside reviews array refers to user id from "users" collection. I want to get users name, email, and address in reviews array like below:
{
reviews: [
{
_id: 5f164395857bcdd1d8674b69,
reviewerId: 5f15b28d534b5886c0d9eb8a
reviewer : {
name:"some_name",
email:"abc#example.com"
}
},
{
_id: 5f164395857bcdd1d8674b6a,
reviewerId: 5f1358c523dc2367c43a6311
reviewer : {
name:"some_name",
email:"efg#example.com"
}
}
]
}
How can I achieve it?
Hopefully, the structure of your collection almost similar as I mention below in the Mongo playground.
db.reviews.aggregate([
{
$unwind: {
path: "$reviews",
preserveNullAndEmptyArrays: false
}
},
{
$lookup: {
from: "user",
localField: "reviews.reviewerId",
foreignField: "_id",
as: "reviews.reviewer"
}
},
{
$group: {
_id: "$_id",
question: {
$first: "$question"
},
reviews: {
$addToSet: "$reviews"
}
}
}
])
Working Mongo playground
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
I have two collections
User
{
"_id" : ObjectId("584aac38686860d502929b8b"),
"name" : "John"
}
Role
{
"_id" : ObjectId("584aaca6686860d502929b8d"),
"role" : "Admin",
"userId" : "584aac38686860d502929b8b"
}
I want to join these collection based on the userId (in role collection) - _id ( in user collection).
I tried the below query:
db.role.aggregate({
"$lookup": {
"from": "user",
"localField": "userId",
"foreignField": "_id",
"as": "output"
}
})
This gives me expected results as long as i store userId as a ObjectId. When my userId is a string there are no results.
Ps: I tried
foreignField: '_id'.valueOf()
and
foreignField: '_id'.toString()
. But no luck to match/join based on a ObjectId-string fields.
Any help will be appreciated.
You can use $toObjectId aggregation from mongodb 4.0 which converts String id to ObjectId
db.role.aggregate([
{ "$lookup": {
"from": "user",
"let": { "userId": "$_id" },
"pipeline": [
{ "$addFields": { "userId": { "$toObjectId": "$userId" }}},
{ "$match": { "$expr": { "$eq": [ "$userId", "$$userId" ] } } }
],
"as": "output"
}}
])
Or you can use $toString aggregation from mongodb 4.0 which converts ObjectId to String
db.role.aggregate([
{ "$addFields": { "userId": { "$toString": "$_id" }}},
{ "$lookup": {
"from": "user",
"localField": "userId",
"foreignField": "userId",
"as": "output"
}}
])
This is not possible as of MongoDB 3.4. This feature has already been requested, but hasn't been implemented yet. Here are the corresponding tickets:
SERVER-22781: Allow $lookup between ObjectId (_id.str) and
string
SERVER-24947: Need a type conversion mechanism for booleans,
ISODates, ObjectID
For now you'll have to store userId as ObjectId
EDIT
The previous tickets were fixed in MongoDB 4.0. You can now achieve this with the folowing query:
db.user.aggregate([
{
"$project": {
"_id": {
"$toString": "$_id"
}
}
},
{
"$lookup": {
"from": "role",
"localField": "_id",
"foreignField": "userId",
"as": "role"
}
}
])
result:
[
{
"_id": "584aac38686860d502929b8b",
"role": [
{
"_id": ObjectId("584aaca6686860d502929b8d"),
"role": "Admin",
"userId": "584aac38686860d502929b8b"
}
]
}
]
try it online: mongoplayground.net/p/JoLPVIb1OLS
I think the previous answer has an error on the $toObjectId case. The let statement applies to the db collection on which the function aggregate is called (i.e 'role') and not on the collection pointed by "from" (i.e 'user').
db.role.aggregate([
{ "$lookup": {
"let": { "userObjId": { "$toObjectId": "$userId" } },
"from": "user",
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$userObjId" ] } } }
],
"as": "userDetails"
}}
])
Or
db.role.aggregate([
{ "$project": { "userObjId": { "$toObjectId": "$userId" } } },
{ "$lookup": {
"localField": "userObjId",
"from": "user",
"foreignField": "$_id",
"as": "userDetails"
}}
])
And
db.user.aggregate([
{ "$project": { "userStrId": { "$toString": "$_id" }}},
{ "$lookup": {
"localField": "userStrId",
"from": "role",
"foreignField": "userId",
"as": "roleDetails"
}}
])
For example we have two collections:
Authors like {'_id': ObjectId(), name: 'John Smith'}
Messages like {'_id': ObjectId(), text: 'Message Text', authorId: 'stringId'}
And we need to get messages with author names and other data
Then:
db.messages.aggregate([
{
$addFields: {
'$authorObjectId': {$toObjectId: $authorId}
}
},
{
$lookup: {
from: 'authors',
localField: '$authorObjectId',
foreignField: '_id',
as: 'author'
}
}
])
Explained:
Our aggregation pipeline has two steps:
First: We add additional field to messages which contains converted string authorId to ObjectId()
Second: We use this field as localField (in messages) to be compared with foreignField '_id' (in authors)