I have seen a ton of question about the $lookup aggregator for arrays of ObjectIds but I can't seem to find anything about when the ObjectId is inside an array of embedded documents.
I have the follow document inside a mongodb database:
_id: ObjectId('...')
Alarms: [
{
Gateway: ObjectId('...')
Port: 1
},
{
Gateway: ObjectId('...')
Port: 2
}
]
I would like to have the following:
_id: ObjectId('...')
Alarms [
{
Gateway: ...(Gateway Object),
Port: 1
},
{
Gateway: ...(Gateway Object),
Port: 2
}
]
I have tried the following with no success:
$lookup: {
from: 'Gateway',
localField: 'Alarms.Gateway',
foreignField: '_id',
as: 'Alarms.Gateway'
}
But this gives me the following result:
_id: ObjectId('...')
Alarms [
{
Gateway: {
...(Gateway Object)
}
Port: 1
}
]
Please try the below queries :
If you don't want the object which doesn't have match in Gateway collection exist in Alarms array in final result :
db.Alarms.aggregate([{ $unwind: '$Alarms' }, {
$lookup: {
from: 'Gateway',
localField: 'Alarms.Gateway',
foreignField: '_id',
as: 'Alarms.Gateway'
}
}, { $match: { 'Alarms.Gateway': { $ne: [] } } },
{ $addFields: { 'Alarms.Gateway': { $arrayElemAt: ['$Alarms.Gateway', 0] } } },
{ $group: { _id: '$_id', Alarms: { $push: '$Alarms' } } }
])
Test : MongoDB-Playground
Otherwise, if you want all objects in Alarms array to be returned irrespective of whether there is a match in Gateway or not :
db.Alarms.aggregate([{ $unwind: '$Alarms' }, {
$lookup: {
from: 'Gateway',
localField: 'Alarms.Gateway',
foreignField: '_id',
as: 'Alarms.GatewayObj'
}
}, { $addFields: { 'Alarms.Gateway': { $cond: [{ $ne: ['$Alarms.GatewayObj', []] }, { $arrayElemAt: ['$Alarms.GatewayObj', 0] }, '$Alarms.Gateway'] } } },
{ $project: { 'Alarms.GatewayObj': 0 } },
{ $group: { _id: '$_id', Alarms: { $push: '$Alarms' } } }
])
Test : MongoDB-Playground
Difference between two queries would be one will return below object in Alarms array (Vs) one don't.
{
"Gateway": ObjectId("5e2b5425d02e05b6940de2fb"),
"Port": 2
}
Related
I have these collections:
author
[
{
_id: "63c242130b17d5516e0cb499",
author_name:'Vyom',
book_ids:["63c242330b17d5516e0cb49a","63c242410b17d5516e0cb49b"]
}
]
book
[
{
_id:"63c242330b17d5516e0cb49a",
author_id:'63c242130b17d5516e0cb499',
book_name:'True Love',
genere:'horror'
},
{
_id:"63c242410b17d5516e0cb49b",
author_id:'63c242130b17d5516e0cb499',
book_name:'Monster Strike',
genere:'romance'
},
]
I want to fetch details of books in author collection aggregation if book_ids exists.
For this I tried as:
db.author.aggregate([
{
$match: {
_id: ObjectId("63c242130b17d5516e0cb499")
}
},
{
$lookup:{
from: 'book',
localField: '_id',
foreignField: 'author_id',
as: 'book_details'
}
},
{
$addFields:{
book_info: {
$map: {
input: '$book_details'
as: 'el'
in: {
$match: {_id:ObjectId('$$el._id')},
$paroject: {book_name: 1},
}
}
}
}
}
])
But it throws:
Unrecognized error: '$match'.
Expected O/P:
[
{
_id: "63c242130b17d5516e0cb499",
author_name:'Vyom',
book_ids:["63c242330b17d5516e0cb49a","63c242410b17d5516e0cb49b"],
book_info: [
{
_id:"63c242330b17d5516e0cb49a",
book_name:'True Love',
},
{
_id:"63c242410b17d5516e0cb49b",
book_name:'Monster Strike',
}
]
}
]
Is there any other way to loop and get details? I tried looking for other solutions but was unable to find.
I don't see why you need a $set stage to format the element in the book_details array for the book_info field.
You can use $lookup with pipeline to join both collections and format the array of documents returned.
db.author.aggregate([
{
$match: {
_id: ObjectId("63c242130b17d5516e0cb499")
}
},
{
$lookup: {
from: "book",
localField: "book_ids",
foreignField: "_id",
as: "book_info",
pipeline: [
{
$project: {
_id: 1,
book_name: 1
}
}
]
}
}
])
Demo # Mongo Playground
db.authors.aggregate([
{
$lookup: {
from: "books",
localField: "book_ids",
foreignField: "_id",
as: "books"
}
},
{
$match: { "books": { $ne: [] } }
},
{
$project: {
_id: 1,
author_name: 1,
books: {
_id: 1,
author_id: 1,
book_name: 1,
genre: 1
}
}
}
])
This pipeline will give you the result where all the authors' book details will be there if they have any books
I have collections like this:
// tasks
[
{_id: '123', _user: '345', _solutions: ['567', '678'] }
]
// solutions
[
{ _id: '567', _task: '123', _user: '345' },
{ _id: '678', _task: '123', _user: '345' }
]
// users
[
{ _id: '345', name: 'Tom' }
]
With this code:
await db
.collection<Task>('tasks')
.aggregate([
{ $match: { _id: id } },
{
$lookup: {
from: 'solutions',
// I guess here should be pipeline
localField: '_solutions',
foreignField: '_id',
as: '_solutions',
},
},
{ $lookup: { from: 'users', localField: '_user', foreignField: '_id', as: '_user' } },
])
I have result as below:
task = {
_id: 5e14e877fa42402079e38e44,
_solutions: [
{
_id: 5e15022ccafcb4869c153e61,
_task: 5e14e877fa42402079e38e44,
_user: 5e007403fd4ca4f47df69913, <-- this should be userObject instead
},
{
_id: 5e164f31cafcb4869c153e62,
_task: 5e14e877fa42402079e38e44,
_user: 5e007403fd4ca4f47df69913, <-- this should be userObject instead
}
],
_user: [
{
_id: 5e007403fd4ca4f47df69913,
_solutions: [Array],
_tasks: [Array],
}
]
}
and I don't know how to $lookup into _solutions._user - so instead of objectId I will have exact user object.
You can run $lookup with custom pipeline for outer lookup and the regular one for users:
db.tasks.aggregate([
{
$match: {
_id: "123"
}
},
{
$lookup: {
from: "solutions",
let: { solutions: "$_solutions" },
pipeline: [
{ $match: { $expr: { $in: [ "$_id", "$$solutions" ] } } },
{
$lookup: {
from: "users",
localField: "_user",
foreignField: "_id",
as: "_user"
}
},
{ $unwind: "$_user" }
],
as: "_solutions",
}
}
])
Mongo Playground
I'm having a Model which is structured similar to this:
{
"_id": ObjectId("5c878c5c18a4ff001b981zh5"),
"books": [
ObjectId("5d963a7544ec1b122ab2ddc"),
ObjectId("5d963be01f663d168f8ea4dc"),
ObjectId("5d963bcb1f663d168f8ea2f4"),
ObjectId("5d963bdf1f663d16858ea7c9"),
}
Now I want to use the aggregation framework to get a list of only the populated books, like:
{ _id: ObjectId("5d963a7544ec1b122ab2ddc"), title: ...., ... },
..
.aggregate([
{
$lookup: {
from: 'books',
let: { books: '$books' },
pipeline: [{ $match: { $expr: { _id: { $in: ['_id', '$$books'] } } } }],
as: 'bookInfos'
}
},
{ $unwind: '$bookInfos' },
{ $replaceRoot: { newRoot: '$bookInfos' } }
])
I am not too sure about your question, but I think this might be what you're looking for.
So this query worked for me:
{
$match: {
_id: user._id,
},
},
{
$lookup: {
from: "books",
localField: "books",
foreignField: "_id",
as: "booksInfo",
},
},
{ $unwind: "$booksInfo" },
{
$replaceRoot: {
newRoot: "$booksInfo",
},
},
Thanks #zishone. Somehow your query returned all the books available in the db and not only the ones from the User Model, but it works as desired when looking up the documents with local and foreignField.
I'm making a blog and have an query about which would give me better performace, simple lookup or lookup with pipeline because sometime simple lookup gave me fast result and sometime pipleline lookup. So, I am bit confused now which one to use or where to use. Suppose I have 2 collection, user and comment collection.
// Users Collection
{
_id: "MONGO_OBJECT_ID",
userName: "Web Alchemist"
}
// Comments Collection
{
_id: "MONGO_OBJECT_ID",
userId: "USER_MONGO_OBJECT_ID",
isActive: "YES", // YES or NO
comment: "xyz"
}
Now I want to Lookup from users collection to comments, which one would be better for this. I made two query which giving me same result.
[
{
$match: { _id: ObjectId("5d68c019c7d56410cc33b01a") }
},
{
$lookup: {
from: "comments",
as: "comments",
localField: "_id",
foreignField: "userId"
}
},
{
$unwind: "$comments"
},
{
$match: {
"comments.isActive": "YES"
}
},
{ $limit: 5},
{
_id: 1, userName: 1, comments: { _id: "$comments._id", comment: "$comments.comment"}
},
{
$group: {
_id: "$_id",
userName: { '$first': '$userName' },
comments: { $addToSet: "comments"}
}
}
]
OR
[
{
$match: { _id: ObjectId("5d68c019c7d56410cc33b01a") }
},
{
$lookup: {
from: "comments",
as: "comments",
let: { userId: "$_id" },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$userId', '$$userId'] },
{ $eq: ['$isActive', 'YES'] }
]
}
}
},
{ limit: 5 },
{
$project: { _id: 1, comment: 1 }
}
]
}
}
]
I have the next collection for exaple:
// vehicles collection
[
{
"_id": 321,
manufactor: SOME-OBJECT-ID
},
{
"_id": 123,
manufactor: ANOTHER-OBJECT-ID
},
]
And I have a collection named tables:
// tables collection
[
{
"_id": SOME-OBJECT-ID,
title: "Skoda"
},
{
"_id": ANOTHER-OBJECT-ID,
title: "Mercedes"
},
]
As you can see, the vehicles collection's documents are pulling data from the
tables's collection ducments - the first document in the vehicles collection has a manufactor
id which is getting pulled from the tables collection and named Skoda.
That is great.
When I am querying the DB using aggregate I can able to easily pull the remote data from the remote collections
respectively - without any problem.
I can also easily make rules and limitations like $project, $sort, $skip, $limit and others.
But I want to display to the user only those vehicles that are manufcatord by Mercedes.
Since Mercedes is not mentioned in the vehicles collection, but only its ID, the $text $search would not
return with the right results.
This is the aggregate pipeline that I provide:
[
{
$match: {
$text: {
$search: "Mercedes"
}
}
},
{
$lookup: {
from: "tables",
let: {
manufactor: "$manufactor"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$_id", "$$manufactor"
]
}
}
},
{
$project: {
title: 1
}
}
],
as: "manufactor"
},
},
{
$unwind: "$manufactor"
},
{
$lookup: {
from: "tables",
let: {
model: "$model"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$_id", "$$model"
]
}
}
},
{
$project: {
title: 1
}
}
],
as: "model"
},
},
{
$unwind: "$model"
},
{
$lookup: {
from: "users",
let: {
joined_by: "$_joined_by"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$_id", "$$joined_by"
]
}
}
},
{
$project: {
personal_info: 1
}
}
],
as: "joined_by"
},
},
{
$unwind: "$joined_by"
}
]
As you can see I am using the $text and $search $match at the first stage in the pipleline - otherwise
MongoDB will throw an error.
But this $text $search object searhed only in the origin collection - the vehicles collection.
Is there a way to tell MongoDB to search in the remote collection with the $text and $search method
and then put in the aggregate only results that are matching both?
UPDATE
When I am doing this instead:
{
$lookup: {
from: "tables",
pipeline: [
{
$match: {
$text: {
$search: "Mercedes"
}
}
},
{
$project: {
title: 1
}
}
],
as: "manufactor"
},
},
This is what I receive:
MongoError: pipeline requires text score metadata, but there is no text score available
if you are using one of the affected versions in this thread, you need to update your mongodb server.
As you can see the issue was fixed in version 4.1.8