I am trying to perform lookup on MongoDb between two collections where one of the fields as attribute as an key-value pair where i have to choose only key as my local field parameter.
Sample Json:
key of the below json
{ "distributions" : {
"5cf88" : "5d023d4aa",
"5cfaca42e" : "5d0093a",
"5d023d490d" : "5d22abc69093a"
}
}
_id field of the below json:
{
"_id" : "5d22abc69093a",
"activatedBy" : {
"id" : "5bc53813055aec",
"name" : "Test1",
"roles" : [
"root"
]
}
}
Lookup query:
$lookup: {
from: 'collecection1',
localField: 'distributions.key',
foreignField: '_id',
as: 'Join'
}
How to get they key form distributions to use for lookup as i need only key of as my join parameter?
How to get they key form distributions to use for lookup as i need
only key of as my join parameter?
This aggregation query can do that using the $objectToArray aggregation operator:
db.collection1.aggregate( [
{ $addFields: { fieldNameValues: { $objectToArray: "$$ROOT" } } },
{ $unwind: "$fieldNameValues" },
{ $match: { $expr: { $eq: [ { $type: "$fieldNameValues.v" } , "object" ] } } },
{ $addFields: { objs: { $objectToArray: "$fieldNameValues.v" } } },
{ $unwind: "$objs" },
{ $project: { distributions: "$objs" } },
{ $lookup: {
from: 'collection2',
localField: 'distributions.v',
foreignField: '_id',
as: 'Join'
}
}
] )
where:
collection1:
{ "distributions" : {
"5cf88" : "5d023d4aa",
"5cfaca42e" : "5d0093a",
"5d023d490d" : "5d22abc69093a"
}
}
collection2:
{
"_id" : "5d22abc69093a",
"activatedBy" : {
"id" : "5bc53813055aec",
"name" : "Test1",
"roles" : [
"root"
]
}
}
Related
Cat Collection:
{
"_id" : ObjectId("5ee8d0d16e4fec1ad4779249"),
"description" : ObjectId("5ea9af047d6a4f6480fd42f4")
}
Description Collection:
{
"_id" : ObjectId("5ea9af047d6a4f6480fd42f4"),
"color" : "ginger"
}
I'd like to obtain all in Cat collection where the Cat.description.color property equals ginger and the Cat.description property is joined;
{
"_id" : ObjectId("5ee8d0d16e4fec1ad4779249"),
"description" : {
"_id" : ObjectId("5ea9af047d6a4f6480fd42f4"),
"color" : "ginger"
}
}
I have the following aggregate query which works, but seems inefficient due to the second $lookup. Given the $match provides us with the necessary Description objects, is there a better way ?
db.Description.aggregate(
{
$match:{
$and:[
{ color: 'ginger' }
]
}
},
{
$lookup: {
from: 'Cat',
let: { descId: '$_id' },
pipeline: [
{
$match: {
$expr: {
$eq: ['$description', '$$descId']
}
}
}
],
as: 'cat'
}
},
{
$unwind: '$cat'
},
{
$replaceRoot: {
newRoot: '$cat'
}
},
{
$lookup: {
from: 'Description',
localField: 'description',
foreignField: '_id',
as: 'description'
}
},
{
$unwind: '$description'
});
You can aggregate on Cat collection and get the expected result
db.cat.aggregate([
{
$lookup: {
from: "description",
let: { catId: "$description" },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: [ "$_id", "$$catId" ] },
{ $eq: [ "$color", "ginger" ] }
]
}
}
}
],
as: "description"
}
},
])
Working Mongo playground
Trying to do mongodb aggregate $lookup query for following collections :
Shop collection :
{
"_id" : ObjectId("5b618a57759612021aaa2ed"),
"no" : "23456",
"date" : ISODate("2012-01-04T16:00:00.000+0000"),
"clientId" : "5b55cc5c05546200217ae0f3"
}
Client collection :
{
"_id" : ObjectId("5b55cc5c05546200217ae0f3"),
"title" : "Ms",
"name" : "Jane Marie"
}
the query :
db.getCollection("shop").aggregate([
{ $lookup:
{
from: "client",
localField: "clientId",
foreignField: "_id",
as: "client"
}
}
])
above query ends up giving an empty patient array :
{
"_id" : ObjectId("5b618a57759672021aaa2ed"),
"no" : "20190000024274",
"date" : ISODate("2012-01-04T16:00:00.000+0000"),
"clientId" : "5b55cc5c05546200217ae0f3",
"client" : []
}
Edit :
and when trying to lookup using an array of ids as local Field :
transaconsIds: ["5b61d4320550de077143b763", "5b61d4324450de002143b777"]
by using :
{
$lookup:
{
from: "transcation",
let: { vid: "transaconsIds" },
pipeline: [
{
$match: {
$expr: {
$eq: ["$_id", { $toObjectId: "$$vid" }]
}
}
}
],
as: "transactions"
}
}
this leads to an Mongo Server error.
Edit 02 :
when trying to lookup for localField which is a nested as follows :
"transactions" : [
{
"bill" : {
"soldItemIds" : [
"5b55aabf0550770021097ed2"
]
}
}
]
by using :
{ $unwind : "$transactions"},
{
$lookup:
{
from: "bill",
let: { did: "$transactions.bill.soldItemIds" },
pipeline: [
{
$match: {
$expr: {
$in: ["$_id", {
$map: {
input: "$$did",
in: { $toObjectId: "$$this" }
}
}
]
}
}
}
],
as: "bills"
}
}
this leads to an Mongo Server error too.
this should do it:
db.shop.aggregate([
{
$lookup:
{
from: "client",
let: { pid: "$clientId" },
pipeline: [
{
$match: {
$expr: {
$eq: ["$_id", { $toObjectId: "$$pid" }]
}
}
}
],
as: "client"
}
},
{
$set: {
client: {
$arrayElemAt: ["$client", 0]
}
}
}
])
update: array of id strings
db.collection.aggregate(
[
{
$lookup:
{
from: "transactions",
let: { vid: "$transactionIds" },
pipeline: [
{
$match: {
$expr: {
$in: ["$_id", {
$map: {
input: "$$vid",
in: { $toObjectId: "$$this" }
}
}
]
}
}
}
],
as: "transactions"
}
}
])
db.collection("shop").aggregate([
{ $match: { _id: shopId } },
{ $addFields: {
convertedId: {$toObjectId: "$clientId"}
}},
{ $lookup: {
from: "users",
localField: "convertedId",
foreignField: "_id",
as: "result"
}}
]);
I have a MongoDB database that is populated by a Spring application using Spring Data. I want to perform a manual query to join two collections and extract some statistics from this data.
The first collection is named emailCampaign and contains this information (simplified):
{
"_id" : ObjectId("5db85687307b0a0d184448db"),
"name" : "Welcome email",
"subject" : "¡Welcome {{ user.name }}!",
"status" : "Sent",
"_class" : "com.mycompany.EmailCampaign"
}
The second collection is named campaignDelivery and contains this information (simplified):
/* 1 */
{
"_id" : ObjectId("5db183fb307b0aef3113361f"),
"campaign" : {
"$ref" : "emailCampaign",
"$id" : ObjectId("5db85687307b0a0d184448db")
},
"deliveries" : 3,
"_class" : "com.mycompany.CampaignDelivery"
}
/* 2 */
{
"_id" : ObjectId("5db85f2c307b0a0d184448e1"),
"campaign" : {
"$ref" : "emailCampaign",
"$id" : ObjectId("5db85687307b0a0d184448db")
},
"deliveries" : 5,
"_class" : "com.mycompany.CampaignDelivery"
}
Ultimately I want to obtain the sum of both deliveries field, but by now I'm stuck with the basic JOIN:
db.emailCampaign.aggregate([
{
$lookup: {
from: 'campaignDelivery',
localField: '_id',
foreignField: 'campaign.$id',
as: 'deliveries'
}
}
])
Throws the following error:
FieldPath field names may not start with '$'.
Escaping the dollar had no impact whatsoever, and I can't any examples of fields that start with dollars.
You can workaround it by using uncorrelated $lookup with $objectToArray in the sub-query to access campaign.$id:
db.emailCampaign.aggregate([
{ $lookup: {
from: "campaignDelivery",
let: { id: "$_id" },
pipeline: [
{ $addFields: {
refId: { $arrayElemAt: [
{ $filter: {
input: { $objectToArray: "$campaign" },
cond: { $eq: [ "$$this.k", { $literal: "$id" } ] }
} }
, 0
] }
} },
{ $match: {
$expr: { $eq: [
"$refId.v",
"$$id"
] }
} },
{ $project: {
refId: 0
} }
],
as: "deliveries"
} }
])
The aggregate was executed.
I got the results using lookup, but I need a sort.
In addition, I want to assign an index to the result value.
CollectionA :
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"contents" : [
ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA"),
ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")
],
"name" : "jason"
}
CollectionB :
{
"_id" : ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA")
"title" : "a title",
"date" : 2018-01-02
},
{
"_id" : ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")
"title" : "a title",
"date" : 2018-01-01
}
Query:
db.getCollection('A').aggregate([
{
$match : { "_id" : ObjectId("5a6cf47415621604942386cd") }
},
{
$lookup : {
from: "B",
localField: "contents",
foreignField: "_id",
as: "item"
}
},
{ $sort: { "item.date" : -1 } }
]);
Want Result:
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"contents" : [
{
"_id" : ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")
"title" : "a title",
"date" : 2018-01-01,
"index" : 0
},
{
"_id" : ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA")
"title" : "a title",
"date" : 2018-01-02,
"index" : 1
}],
"name" : "jason"
}
The current problem does not apply to the sort.
And I don't know how to designate an index.
Below Aggregation may you. For your desire result.
db.CollectionA.aggregate([
{
$match: { "_id": ObjectId("5a6cf47415621604942386cd") }
},
{
$lookup: {
from: "CollectionB",
let: { contents: "$contents" },
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$contents"] } }
},
{ $sort: { date: 1 } }
],
as: "contents"
}
},
{
$project: {
contents: {
$map: {
input: { $range: [0, { $size: "$contents" }, 1 ] },
as: "element",
in: {
$mergeObjects: [
{ index: "$$element" },
{ $arrayElemAt: [ "$contents", "$$element" ]}
]
}
}
}
}
}
])
One way to go about it would be to unwind the array, sort it and then group it back
db.A.aggregate([
{
$match: {
"_id": ObjectId("5a6cf47415621604942386cd")
}
},
{
$lookup: {
from: "B",
localField: "contents",
foreignField: "_id",
as: "item"
}
},
{
$unwind: "$item"
},
{
$sort: {
"item.date": -1
}
},
{
$group: {
_id: "$_id",
contents: {
$push: "$item"
}
}
}
])
Another method is, (this is applicable only if the date field corresponds to the document creation date),
db.A.aggregate([
{
$match: {
"_id": ObjectId("5a6cf47415621604942386cd")
}
},
{
$lookup: {
from: "B",
localField: "contents",
foreignField: "_id",
as: "item"
}
},
{
$sort: {
"item": -1
}
}
])
Basically, this sorts on the basis of _id, and since _id is created using the creation date, it should sort accordingly.
I am puzzled as to why the code below doesn't work. Can anyone explain, please?
For some context: My goal is to get the score associated with an answer option for a survey database where answers are stored in a separate collection from the questions. The questions collection contains an array of answer options, and these answer options have a score.
Running this query:
db.answers.aggregate([
{
$match: {
userId: "abc",
questionId: ObjectId("598be01d4efd70a81c1c5ad4")
}
},
{
$lookup: {
from: "questions",
localField: "questionId",
foreignField: "_id",
as: "question"
}
},
{
$unwind: "$question"
},
{
$unwind: "$question.options"
},
{
$unwind: "$answers"
}
])
I get:
{
"_id" : ObjectId("598e588e0c5e24452c9ee769"),
"userId" : "abc",
"questionId" : ObjectId("598be01d4efd70a81c1c5ad4"),
"answers" : {
"id" : 20
},
"question" : {
"_id" : ObjectId("598be01d4efd70a81c1c5ad4"),
"options" : {
"id" : 10,
"score" : "12"
}
}
}
{
"_id" : ObjectId("598e588e0c5e24452c9ee769"),
"userId" : "abc",
"questionId" : ObjectId("598be01d4efd70a81c1c5ad4"),
"answers" : {
"id" : 20
},
"question" : {
"_id" : ObjectId("598be01d4efd70a81c1c5ad4"),
"options" : {
"id" : 20,
"score" : "4"
}
}
}
All great. If I now add to the original query a match that's supposed to find the answer option having the same id as the answer (e.g. questions.options.id == answers.id), things don't work as I would expect.
The final pipeline is:
db.answers.aggregate([
{
$match: {
userId: "abc",
questionId: ObjectId("598be01d4efd70a81c1c5ad4")
}
},
{
$lookup: {
from: "questions",
localField: "questionId",
foreignField: "_id",
as: "question"
}
},
{
$unwind: "$question"
},
{
$unwind: "$question.options"
},
{
$unwind: "$answers"
},
{
$match: {
"question.options.id": "$answers.id"
}
},
{
$project: {
_id: 0,
score: "$question.options.score"
}
}
])
This returns an empty result. But if I change the RHS of the $match from "$answers.id" to 20, it returns the expected score: 4. I tried everything I could think of, but couldn't get it to work and can't understand why it doesn't work.
I was able to get it to work with the following pipeline:
{
$match: {
userId: "abc",
questionId: ObjectId("598be01d4efd70a81c1c5ad4")
}
},
{
$lookup: {
from: "questions",
localField: "questionId",
foreignField: "_id",
as: "question"
}
},
{
$unwind: "$question"
},
{
$unwind: "$question.options"
},
{
$unwind: "$answers"
},
{
$addFields: {
areEqual: { $eq: [ "$question.options.id", "$answers.id" ] }
}
},
{
$match: {
areEqual: true
}
},
{
$project: {
_id: 0,
score: "$question.options.score"
}
}
I think the reason it didn't work with a direct match is the fact that questions.options.id doesn't actually reference the intended field... I needed to use $questions.options.id which wouldn't work as a LHS of a $match, hence the need to add an extra helper attribute.