I've two collections Users and Notes. Both collections contain an id property and the Notes collection has an userid that is the id of some user on the Users collection.
Now, I'm trying to aggregate (join) some user information into Notes:
db.getCollection("Notes").aggregate(
{
"$lookup": {
"from": "Users",
"let": {
"idForeignField": "$id"
},
"pipeline": [
{
"$match": {
"$expr": {
"$and": [{
"$eq": ["$userid", "$$idForeignField"]
}]
}
}
}
],
"as": "Users#joined"
}
}
);
What I get in a empty Users#joined array. Why? Shouldn't my query work? Is the problem caused by the fact that both collections have an id property? If yes how can I tell let and match what is the right collection?
Update: alternatively a simpler query works just fine:
db.getCollection("Notes").aggregate(
{
$lookup:
{
from: "Users",
localField: "userid",
foreignField: "id",
as: "Users#joined"
}
}
);
However I would like to do it with let and a pipeline in order to add more match conditions.
Thank you.
Your let variable must be userid
db.getCollection("Notes").aggregate([
{ "$lookup": {
"from": "Users",
"let": { "ifForeignField": "$userid" },
"pipeline": [
{ "$match": { "$expr": { "$and": [{ "$eq": ["$id", "$$ifForeignField"] }] }}}
],
"as": "Users#joined"
}}
])
Related
i'm using the following aggregation:
const aggregate = [
{
$match: {
mainCatId: new ObjectId(catId),
},
},
{
"$lookup": {
"from": "products",
"pipeline": [
{ "$match": { "subCategory": '$_id' } },
],
"as": "products"
}
},
{ "$unwind": "$products" }
];
The problem is that i have to match the id of each doc in the pipeline section but this is not working. So the question is how can i match the id i"m getting from the match above
Is the syntax below, which uses the traditional syntax for single join conditions, what you are looking for?
const aggregate = [
{
$match: {
mainCatId: new ObjectId(catId),
},
},
{
"$lookup": {
"from": "products",
localField: "subCategory",
foreignField: "_id",
"as": "products"
}
},
{
"$unwind": "$products"
}
]
If not, please provide sample documents and further details about the ways in which your current approach (and this proposed solution) are not working.
Hi im really struggling finding a solution to this.
Im trying to see if an array contains a value that I give
I want all tickets of a specific organization.
The organization is saved in the user (Creator) in an array named organizations.
Models
ticket {
creator: "ObjectId"
}
user {
organizations: ["ObjectId", "ObjectId"]
}
This is what I have now
Ticket.aggregate([
{
"$lookup": {
"from": User.collection.name,
"localField": "creator",
"foreignField": "_id",
"as": "creator"
}
},
{ "$unwind": "$creator" },
{ "$match": { "creator.organizations": {
"$elemMatch": {"$in": ["60a77d5b57d8c960829a0343"]}}}
},
{ "$set": {"creator": "$creator._id"}},
])
This doesn't work tho
I read that you can't use $elemMatch inside an aggregate because its a query.
How do I achieve this? I've seen people saying to use a $filter but I have no clue how to make that.
$elemMatch is not required here. This can be achieved only using $match as shown below.
const mongoose = require("mongoose");
Ticket.aggregate([{
"$lookup": {
"from": User.collection.name,
"localField": "creator",
"foreignField": "_id",
"as": "creator"
}
},
{
"$unwind": "$creator"
},
// Modified code
{
"$match": {
"creator.organizations": mongoose.Types.ObjectId("60a77d5b57d8c960829a0343")
}
},
{
"$set": {
"creator": "$creator._id"
}
},
])
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)
I am new to mongodb so I hope this does not come-off as a very elementary question. I've done some research and tried to apply what I've found but something just seems to escape me.
I have two collections of the following format:
-----------------------------------------------------------------------
Shop
-----------------------------------------------------------------------
{
"shopId": "1002",
"shopPosId": "10002",
"description": "some description"
}
-----------------------------------------------------------------------
Compte
-----------------------------------------------------------------------
{
"shopId": "9000",
"shopPosId": "0000",
"clientUid": "474192"
}
I want to join those and before doing so, I want to filter out the Shops which do not have the shopPosId field.
Here's my code:
Compte.aggregate([
{
$match:
{
$and:[{"clientUid":clientUid}]
}
},
{
$lookup:
{
from: "Shop",
localField: "shopId",
foreignField: "shopId",
let: {"Shop.shopPosId": "$shopPosId"},
pipeline: [{$match: {"shopPosId": {"$exists": false}}}],
as: "shopDescr"
}
}]
);
the returned result is an undefined, which means the query doesn't make much sense (because in fact I should at least get a void array).
Is this because the two collections have the shopPosId field? (if so, isn't this line let: {"Shop.shopPosId": "$shopPosId"} supposed to take care of it ?)
As Alex commented you are mixing both the $lookup syntax here... So the correct will be
Compte.aggregate([
{ "$match": { "$and": [{ "clientUid": clientUid }] }},
{ "$lookup": {
"from": "Shop",
"let": { "shopId": "$shopId" },
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$shopId", "$$shopId" ] },
"shopPosId": { "$exists": false }
}}
],
"as": "shopDescr"
}}
])
I have two collections, article and comments, the articleId in comments is a foreign key of _id in article.
db.collection('article').aggregate([
{
$lookup: {
from: "comments",
localField: "_id",
foreignField: "articleId",
as: "comments"
}
},
...
])
but it doesn't work, because _id in article is an ObjectID and articleId is a string.
You can achieve this using $addFields and $toObjectId aggregations which simply converts string id to mongo objectId
db.collection('article').aggregate([
{ "$lookup": {
"from": "comments",
"let": { "article_Id": "$_id" },
"pipeline": [
{ "$addFields": { "articleId": { "$toObjectId": "$articleId" }}},
{ "$match": { "$expr": { "$eq": [ "$articleId", "$$article_Id" ] } } }
],
"as": "comments"
}}
])
Or using $toString aggregation
db.collection('article').aggregate([
{ "$addFields": { "article_id": { "$toString": "$_id" }}},
{ "$lookup": {
"from": "comments",
"localField": "article_id",
"foreignField": "articleId",
"as": "comments"
}}
])
If you are using MongoDB 4.0 or above then you can directly use $toString on the _id field:
db.collection('article').aggregate([
{
$lookup: {
from: "comments",
localField: { $toString : "_id" },
foreignField: "articleId",
as: "comments"
}
},
...
])
But MongoDB 3.6 doesn't support type conversion inside an aggregation pipeline. So $toString and $convert will only work with MongoDB 4.0.