I'm doing a $lookup from a _id. So the result is always 1 document. Hence, I want the result to be an object instead an array with one item.
let query = mongoose.model('Discipline').aggregate([
{
$match: {
project: mongoose.Types.ObjectId(req.params.projectId)
},
},
{
$lookup: {
from: "typecategories",
localField: "typeCategory",
foreignField: "_id",
as: "typeCategory"
}
},
{
$project: {
title: 1, typeCategory: "$typeCategory[0]"
}
}
]);
This notation: "$typeCategory[0]" is not working. Is there any smart way of doing this?
You can just use $unwind. It deconstructs an array field from the input documents to output a document for each element
let query = mongoose.model('Discipline').aggregate([
{
$match: {
project: mongoose.Types.ObjectId(req.params.projectId)
},
},
{
$lookup: {
from: "typecategories",
localField: "typeCategory",
foreignField: "_id",
as: "typeCategory"
}
},
{$unwind: '$typeCategory'},
{
$project: {
title: 1, typeCategory: "$typeCategory"
}
}
]);
You can use $arrayElemAt in $project stage.
Syntax of $arrayElemAt is { $arrayElemAt: [ <array>, <idxexOfArray> ] }
like:
mongoose.model('Discipline').aggregate([
{
$match: {
project: mongoose.Types.ObjectId(req.params.projectId)
},
},
{
$lookup: {
from: "typecategories",
localField: "typeCategory",
foreignField: "_id",
as: "typeCategory"
}
},
{
$project: {
name: 1, typeCategory: {$arrayElemAt:["$typeCategory",0]}
}
}
]);
Use $first to return the first element in the array:
$project: {
title: 1,
typeCategory: {$first: "$typeCategory"}
}
For merging two collections:
{$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$typeCategory", 0 ] }, "$$ROOT" ] } } },
{ $project: { typeCategory: 0 } }
Related
I currently have a Mongo query that looks like this:
const user = await User.findOne({ userId }).lean() || []
const contributions = await Launch.aggregate([
{ $sort: { addedAt: -1 } },
{ $limit: 10 },
{
$match: {
_id: { $in: user.contributions }
}
},
{
$addFields: {
activity: 'contribution',
launchName: '$name',
launchId: '$_id',
date: '$addedAt',
content: '$description'
}
}
])
But instead of having two different Mongo queries (findOne and aggregate), how can I combine them into one query?
I tried this but it just errors out immediately in the lookup part:
const contributions = await Launch.aggregate([
{ $sort: { addedAt: -1 } },
{ $limit: 10 },
{
$lookup: {
from: 'user',
let: { id: $user.contributions },
pipeline: [
{ $match: { $expr: { $in: [$_id, $$user.contributions] } } }
],
localField: '_id',
foreignField: 'userId',
as: 'user'
}
},
{
$addFields: {
activity: 'contribution',
launchName: '$name',
launchId: '$_id',
date: '$addedAt',
content: '$description'
}
}
])
I've never used the pipeline option so a little confused onn how to fix this problem?
Enclose these $user.contributions, $_id with quotes in order to make the query valid.
Since you declare the id variable with the value of user.contributions. You should use the variable with $$id instead of $$user.contributions.
I don't think the localField and foreignField are needed as you are mapping/joining with pipeline.
Your aggregation query should be looked as below:
const contributions = await Launch.aggregate([
{ $sort: { addedAt: -1 } },
{ $limit: 10 },
{
$lookup: {
from: 'user',
let: { id: "$user.contributions" },
pipeline: [
{ $match: { $expr: { $in: ["$_id", "$$id"] } } }
],
as: 'user'
}
},
{
$addFields: {
activity: 'contribution',
launchName: '$name',
launchId: '$_id',
date: '$addedAt',
content: '$description'
}
}
])
I am currently searching MongoDB to find all the Posts and showing all the COMMENTS array. Each COMMENT has a postedBy nested object ID that refers to the USERS model.
At the moment when I $lookup, I loose all my current data. I wasnt to keep the initial data but then add on the details about the postedBy.
Similar to what population would do on a Post.find().
const bigPosts= await Post.aggregate([
//Code works good
{
$addFields: {
winAmount: {
$round: { $multiply: ['$betAmount', '$odds'] },
},
},
},
{
$lookup: {
from: 'users',
localField: 'postedBy',
foreignField: '_id',
as: 'postedBy',
},
},
{
$unwind: { path: '$postedBy', preserveNullAndEmptyArrays: true },
},
// this is where the issues start
{
$lookup: {
from: 'users',
localField: 'comments.postedBy',
foreignField: '_id',
as: 'comments.postedBy',
},
},
{
$unwind: {
path: '$comments.postedBy',
preserveNullAndEmptyArrays: true,
},
},
{
$project: {
comments: 1,
winAmount: 1,
'postedBy._id': 1,
'postedBy.name': 1,
'postedBy.username': 1,
'postedBy.image': 1,
},
},
])
I am just wondering if its possible to use a $lookup aggregation operator inside of the in field of a $map operator. I am trying to map the VINs inside the carData objects to corresponding document ids in a VIN collection. Is there a way to accomplish this using $lookup inside of $map where I can match the vin field inside my carData objects with vin fields inside of my VIN collection to map to an id as opposed to the vin.
CarData Collection:
carData: [
{vin: 123456789,
make: "Dodge"},
{vin: 987654321,
make: "Honda"}
]
Vin Collection:
[
{
_id: ObjectId("1dsf1234125")
Vin: 123456789
},
{
_id: ObjectId("1dsf1234124")
Vin: 987654321
},
]
Expected result
carData: [
{vin: ObjectId("1dsf1234125"),
make: "Dodge"},
{vin: ObjectId("1dsf1234124"),
make: "Honda"}
]
Using the data samples from the question post, this aggregation returns the expected result (change the collection names appropriately):
db.cars_collection.aggregate([
{
$lookup:
{
from: "vins_collection",
localField: "vin",
foreignField: "Vin",
as: "car_vins"
}
},
{
$project: {
_id: 0,
make: 1,
vin: {
$arrayElemAt: [
{ $map: { input: "$car_vins", in: "$$this._id" } }, 0
]
}
}
},
])
The result will look like this:
{ "make" : "Dodge", "vin" : ObjectId("626a12a1ac1cc4f9d0bbd7e7") }
{ "make" : "Honda", "vin" : ObjectId("626a12a1ac1cc4f9d0bbd7e8") }
Method 1
db.carDatas.aggregate([
{
$unwind: "$carData"
},
{
$lookup: {
from: "Vins",
localField: "carData.vin",
foreignField: "Vin",
as: "docs"
}
},
{
$project: {
make: "$carData.make",
vin: { $first: "$docs._id" },
_id: 0
}
},
{
$group: {
_id: null,
carData: { $push: "$$ROOT" }
}
},
{
$unset: "_id"
}
])
mongoplayground
This is the sample of my noteCollection's document
{
title: "Some title",
body: "Descreptive body",
contributedBy: [
{
contributorId: ObjectId(5353ff535f5f3a5)
}
] // for some reason, this field has to be an array of objects
}
Now, while using $lookup to "lookup" for the contributor, I need the contributorId field
The problem is, I don't know how to express contributedBy[0].contributorId in MongoDB query way.
// lookup aggregation
{
from: 'contributors',
as: 'contributor',
let: {'contributorUserId': '$contributedBy[0].contributorId'}, // here, how to get the value of `contributorId` property of the 0th element in `contributedBy` field
pipeline: [
{$match: {
$expr: {$eq: ['$_id', '$$contributorUserId']}
}}
]
}
If you want to join those two collections by only one contributedBy.contributorId at 0th index, use $arrayElemAt operator:
db.noteCollection.aggregate([
{
$lookup: {
from: 'contributors',
as: 'contributor',
let: {
'contributorUserId': {
$arrayElemAt: ['$contributedBy.contributorId', 0]
}
},
pipeline: [
{
$match: {
$expr: { $eq: ['$_id', '$$contributorUserId'] }
}
}
]
}
}
])
If you want to join the two collections w.r.t to their reference _ids then use $in operator:
db.noteCollection.aggregate([
{
$lookup: {
from: 'contributors',
as: 'contributor',
let: {
'contributorUserIds': '$contributedBy.contributorId'
},
pipeline: [
{
$match: {
$expr: { $in: ['$_id', '$$contributorUserIds'] }
}
}
]
}
}
])
I have a document that looks as follows.
{
_id: "1234",
orderIds: [1,2,3,4,5]
}
I want to publish this document, however, I want to lookup and filter this reactively. With an aggregation in 3.2, it would be something like this:
var id = "1234"
db.users.aggregate([
{ $match: { _id: id } },
{ $unwind: '$orderIds' },
{
$lookup: {
from: 'orders',
as: 'orders',
localField: 'orderIds',
foreignField: '_id'
}
},
{ $match: { 'orders.status': { $ne: 'closed' } } },
{
$group: {
_id: "$_id",
orders: { $push: '$orders' }
}
}
])
However, I can't find a way to do this with plain publications, and mongo 2.6.7 that meteor ships with. Is there a way to do it?