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'] }
}
}
]
}
}
])
Related
I have the following collections in MongoDB (mongoplayground) [characters,guilds]
I want to make a $lookup, that will add rank field to the resulting document, like that:
"members_t": [
{
"_id": ObjectId("5a934e000102030405000000"),
"level": 20,
"name": "test1",
"rank": 1
},
{
"_id": ObjectId("5a934e000102030405000001"),
"level": 40,
"name": "test2",
"rank": 2
}
]
old $lookup syntax can't help me with that, but the following query with the new syntax returns me an empty array in tested field (even without $addFields stage):
{
$lookup: {
from: "characters",
let: {
members_name: "$members.name",
rank: "$members.rank"
},
pipeline: [
{
$match: {
name: "$$members_name"
}
}
],
as: "tested"
}
}
So, is there any option to add an additional field after $lookup stage or not?
(Mongo -v 4.2.3, so the problem is not related with new syntax support)
You were almost close to solving it. Since the members are an array, you need to pass it through $lookup and then conditionally join it per each character with $reduce (You can do this with $filter as well, but then we need aditional stages).
Note: Explanation why we need to use $expr inside $lookup pipeline.
Try this one:
db.guilds.aggregate([
{
$lookup: {
from: "characters",
let: {
members: "$members"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$name",
"$$members.name"
]
}
}
},
{
$addFields: {
rank: {
$reduce: {
input: "$$members",
initialValue: null,
in: {
$cond: [
{
$eq: [
"$$this.name",
"$name"
]
},
"$$this.rank",
"$$value"
]
}
}
}
}
}
],
as: "members_t"
}
}
])
MongoPlayground
I am looking for a query for a $match stage in my aggregation which do almost the same, as in this question, but..
if field (named rank in my case) doesn't exists in document, add document to results
but if field, exists, apply $operator condition (in my case it's $max) to this field, and add all documents that suits this condition to the results.
MongoPlayground with example collection.
Result should be like this:
[
{
"method": 3,
"item": 1,
"rank": 3 //because it has field named rank, and suits condition {rank: $max}
},
{
"method": 4,
"item": 1 //we need this, because document doesn't have rank field at all
},
{
"method": 5,
"item": 1 //we need this, because document doesn't have rank field at all
}
]
Things, that I have tried already:
{
$match: {
$or: [
{item: id, rank: {$exists: true, $max: "$rank"}}, //id === 1
{item: id, rank: {$exists: false}} //id === 1
]
}
}
UPD: As for now, probably I don't limit with $match stage only, $project is also relevant after default match, so I could request every document during $match stage by id no matter, have the doc rank field or not, and then, during $project stage do a "separation" by rank $exists
Try this one:
db.collection.aggregate([
{
$match: {
item: id
}
},
{
$group: {
_id: "$item", //<- Change here your searching field
max: {
$max: "$rank" //<- Change here your field to apply $max
},
data: {
$push: "$$ROOT"
}
}
},
{
$unwind: "$data"
},
{
$match: {
$expr: {
$or: [
{
$eq: [
{
$type: "$data.rank"
},
"missing"
]
},
{
$eq: [
"$data.rank",
"$max"
]
}
]
}
}
},
{
$replaceWith: "$data"
}
])
MongoPlayground
I have found an answer, separated from #Valijon's method, but it's also based on the logic above. My query is:
db.collection.aggregate([
{
$match: {
item: id
}
},
{
$project: {
method: 1,
item: 1,
rank: {
$ifNull: [
"$rank",
0
]
}
}
},
{
$group: {
_id: "$item",
data: {
$addToSet: "$$ROOT"
},
min_value: {
$min: "$rank"
},
max_value: {
$max: "$rank"
}
}
},
{
$unwind: "$data"
},
{
$match: {
$or: [
{
$expr: {
$eq: [
"$data.rank",
"$max_value"
]
}
},
{
$expr: {
$eq: [
"$data.rank",
"$min_value"
]
}
},
]
}
}
])
My query is based on $project stage which gives the empty field value 0. It also could be -1, or any value that isn't used in collection. And then I separate results.
MongoPlayground
I am trying to aggregate on my User collection and $project out the fields I need. Then I want to set let variables in $lookup to be able to use the variable to find matching documents.
I want to do it this way, because I will have many $lookups by the variables.
However I am not able to get this working correctly. What am I doing wrong with the variables?
$project: {
_id: 1,
name: 1,
goal: 1
}
},
{
$lookup: {
from: "goals",
let: { user: "$_id" },
pipeline: [
{
$match: {
"user": "$$user"
}
},
{ $project: { _id: 0, leads: 0 } },
],
as: "goal"
}
},
{
$project: {
_id: 1,
name: 1,
goal: 1
}
},
You need to use $expr to use variable name inside the $match stage.
{ $match: { $expr: { $eq: ['$user', '$$user'] }}}
I'm using $lookup, which may or may not return something. But if it does, i need to check the looked up value and check a specific field to see if it is null, and if it is, set it to something.
I tried this:
{
$lookup:
{
from:
"item_templates",
localField:
"based_on",
foreignField:
"_id",
as:
"template"
}
},
{
$addFields:
{
"template.image.url":
{
$ifNull:
[
"$template.image.url",
"http://via.placeholder.com/400x700/d3d3d3/000000/?text=No%20Image&"
]
}
}
},
but it seems to have no effect on template.image.url (which is an array of objects), it actually returns an array instead, with one element as null.
You can use newer $lookup syntax to achieve the expected result
db.collection.aggregate([
{ "$lookup": {
"from": "item_templates",
"let": { "based_on": "$based_on" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$_id", "$$based_on"] }}},
{ "$addFields": {
"image.url": {
"$ifNull": [
"$image.url",
"http://via.placeholder.com/400x700/d3d3d3/000000/?text=No%20Image&"
]
}
}}
],
"as": "template"
}}
])
Try with $project.
{
$unwind:{
path: "$template",
includeArrayIndex: "arrayIndex",
preserveNullAndEmptyArrays: true
}
},
{
$project:{
template.image.url: {
$ifNull: [
"$template.image.url",
"http://via.placeholder.com/400x700/d3d3d3/000000/?text=No%20Image&"
]
},
}
}
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 } }