Return the last Document From a Lookup - mongodb

db.groups.aggregate([
{
$lookup:
{
from: "posts",
localField: "_id",
foreignField: "group",
as: "post"
}
}
])
I'm getting response for groups and all post like.. [{geoup1,[post's array]}, {group2,[post's array]}]
If there is any post I just want last added post into post collection

You can either use $slice
db.groups.aggregate([
{ "$lookup": {
"from": "posts",
"localField": "_id",
"foreignField": "group",
"as": "post"
}},
{ "$addFields": {
"post": { "$slice": ["$post", -1] }
}}
])
Or with MongoDB 3.6, just return the last post using $lookup in it's non-correlated form:
db.groups.aggregate([
{ "$lookup": {
"from": "posts",
"as": "post",
"let": { "id": "$_id" },
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$$id", "$group" ] }
}},
{ "$sort": { "_id": -1 } },
{ "$limit": 1 }
]
}}
])
The latter is better because you only return the document from the foreign collection that you actually want.
If you are certain you want "singular" then $arrayElemAt is interchangeable with $slice in the initial example but returns the last element instead of the array of the last element only. You can also add it to the second form to just take one element from the pipeline, which is "always" an array:
db.groups.aggregate([
{ "$lookup": {
"from": "posts",
"as": "post",
"let": { "id": "$_id" },
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$$id", "$group" ] }
}},
{ "$sort": { "_id": -1 } },
{ "$limit": 1 }
]
}},
{ "$addFields": {
"post": { "$arrayElemAt": [ "$post", 0 ] }
}}
])
And that way around it's the 0 index rather than -1 for last.

Related

Dynamic from in $lookup

I am trying to see if i can change the from in the $lookup or rearrange my query to somehow retrieve from three potential collections. So far i have managed to set up the query like so:
const search = db.collection("search");
search.aggregate([
{
'$match': {
'id_int': 0
}
}, {
'$project': {
'_id': 0,
'collection': 1,
'id_int': 1
}
}, {
'$lookup': {
'from': 'arxiv',
'localField': 'id_int',
'foreignField': 'id_int',
'as': 'arxiv'
}
}
], function(err, cursor) ... )
The $match and then $project pipeline stages return a result with the following properties:
collection:"arxiv"
id_int:0
The collection value will always be one of three arxiv, crossref or pmc_test. Therefore i'd like my $lookup from to use this property value programmatically as opposed having it hard coded.
'$lookup': {
'from': 'arxiv' or 'crossref' or 'pmc_test', // Dynamic based on result
...
}
Thanks
Edit
id_int will get passed in and collection will not, thats why a query is made to the search collection.
Sadly this is not possible currently, there is an open feature request on it here so you can keep track of it if you wish.
Right now thought you have two options.
Split your call into 2 queries and add that bit of logic to your code, which is what i personally recommend.
Use this aggregate which looks up all 3 collections:
search.aggregate([
{
'$match': {
'id_int': 0
}
},
{
'$project': {
'_id': 0,
'collection': 1,
'id_int': 1
}
},
{
"$facet": {
"arxiv": [
{
"$lookup": {
"from": "arxiv",
"localField": "id_int",
"foreignField": "id_int",
"as": "arxiv"
}
}
],
"crossref": [
{
"$lookup": {
"from": "crossref",
"localField": "id_int",
"foreignField": "id_int",
"as": "crossref"
}
}
],
"pmc_test": [
{
"$lookup": {
"from": "pmc_test",
"localField": "id_int",
"foreignField": "id_int",
"as": "pmc_test"
}
}
]
}
},
{
"$addFields": {
"newRoot": [
{
"k": "$collection",
"v": {
"$cond": [
{
"$eq": [
"$collection",
"arxiv"
]
},
"$arxiv",
{
"$cond": [
{
"$eq": [
"$collection",
"crossref"
]
},
"$crossref",
"$pmc_test"
]
}
]
}
},
{
"k": "collection", "v": "$collection"
},
{
"k": "id_int", "v": "$id_int"
}
]
}
},
{
"$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$concatArrays": "$newRoot"
}
}
}
}
])
As you might have noticed the pipeline isn't exactly sexy, if you don't care about the field name in the end result you can dump most of it.

how to sort aggregate - Mongodb

db.getCollection('shows').aggregate([
{ $match: { _id: ObjectId("5d622cecbbe890f60ccd1ca4") } },
{ $lookup: { from: "episode", // collection name in db
localField: "_id",
foreignField: "show_id",
as: "episode"
}
},
{ $sort: { 'episode._id': 1 } }
])
So the below works fine however it seems that the sort is not sorting the collection episode in the correct order. It is still putting it oldest to newest when I want to have it newest to oldest.
I am wondering how this is done?
You can use below aggregation
db.getCollection("shows").aggregate([
{ "$match": { "_id": ObjectId("5d622cecbbe890f60ccd1ca4") } },
{ "$lookup": {
"from": "episode",
"let": { "episodeId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$show_id", "$$episodeId"] }}},
{ "$sort": { "_id": 1 }}
],
"as": "episode"
}}
])

How to perform nested lookup in mongo query?

This is my aggregation query
db.user.aggregate([
{ $addFields: { user_id: { $toString: "$_id" } } },
{
$lookup: {
from: "walletData",
let: { id: "$user_id" },
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: ["$userId", "$$id"]
},
{
$gt: ["$lastBalance", 0]
}
]
}
}
}
],
as: "balance"
}
}
])
I get the desired output from this result but need to join one more collection in this query. How can i achieve that?
For example consider these collections:
user : {
"_id": ObjectId("xyz")
}
walletData:{
"userId": "xyz",
"lastBalance": 5
}
AnotherWalletdata:{
"userId": "xyz",
"lastBalance": 6
}
I got the result after joining first two tables how do i join the third table only if the balance of the second table(walletData) is greater than zero?
Expected Output :
{"id":"xyz",
"walletdataBal":5,
"AnotherWalletDataBal":6
}
You can join any number of collections by using only $lookup and $unwind one after another followed by Conditional Projection for whatever that's required at last. Below is the well-tested and working solution for the same :
db.user.aggregate([
{$lookup: {from: "walletData", localField: "_id", foreignField: "userId", as: "walletDataBal"}},
{$unwind: "$walletDataBal"},
{$lookup: {from: "anotherwalletData", localField: "_id", foreignField: "userId", as: "anotherWalletDataBal"}},
{$unwind: "$anotherWalletDataBal"},
{$project: {"id": "$_id", "_id": 0, walletDataBal: "$walletDataBal.lastBalance",
anotherWalletDataBal: {$cond:
{if: { $gt: [ "$walletDataBal.lastBalance", 0 ] },
then: "$anotherWalletDataBal.lastBalance",
else: "$$REMOVE" }}}
]).pretty();
You can add another $lookup stage to achieve the output
db.user.aggregate([
{ "$addFields": { "user_id": { "$toString": "$_id" } } },
{ "$lookup": {
"from": "walletData",
"let": { "id": "$user_id" },
"pipeline": [
{ "$match": {
"$expr": {
"$and": [
{ "$eq": ["$userId", "$$id"] },
{ "$gt": ["$lastBalance", 0] }
]
}
}}
],
"as": "balance"
}},
{ "$lookup": {
"from": "anotherWalletData",
"let": { "id": "$user_id" },
"pipeline": [
{ "$match": {
"$expr": {
"$and": [
{ "$eq": ["$userId", "$$id"] },
{ "$gt": ["$lastBalance", 0] }
]
}
}}
],
"as": "anotherWalletData"
}},
{ "$project": {
"walletdataBal": { "$arrayElemAt": ["$balance.lastBalance", 0] },
"anotherwalletdataBal": {
"$arrayElemAt": ["$anotherWalletData.lastBalance", 0]
}
}}
])
{ $unwind: "$balance" },
{ $lookup: {
from: "walletData",
localField: "balance.userId",
foreignField: "userId",
as:"anotherwalletData"
}}
])
I solved my query, I had to apply unwind after the above lookup.

Populate specific fields in $lookup

I am using aggregate to group and populate the result like below:
{
"$group": {
"_id": "$userId",
"projectId": { "$push": "$projectId" }
}
},
{
"$lookup": {
"from": "users",
"localField": "_id",
"foreignField": "_id",
"as": "user"
}
},
{ $unwind:"$user" },
{
"$lookup": {
"from": "projects",
"localField": "projectId",
"foreignField": "_id",
"as": "projects"
}
}
But i want to populate specific fields from that result For this I tried
$project, But it combining projectId into one array and projectName into another array.Below is my result json:
[
{
"_id": "5c0a29e597e71a0d28b910aa",
"projectId": [
"5c0a2a8897e71a0d28b910ac",
"5c0a4083753a321c6c4ee024"
],
"user": {
"_id": "5c0a29e597e71a0d28b910aa",
"firstName": "Amit"
"lastName": "kumar",
"type": "developer",
"status": "active"
},
"projects": [
{
"_id": "5c0a2a8897e71a0d28b910ac",
"skypeId": "",
"projectName": "LN-PM",
"status": "ongoing",
"assignId": "5c0a2a0a97e71a0d28b910ab"
},
{
"_id": "5c0a4083753a321c6c4ee024",
"skypeId": "",
"status": "pending",
"assignId": "5c0a2a0a97e71a0d28b910ab"
}
]
}
]
Now i want to get the only "firstName and _id" field from user field and "projectName and _id" field from the projects field
You can use below aggregation with mongodb 3.6 and above
With the newer $lookup syntax you can use $projection inside the $lookup pipeline
db.collection.aggregate([
{ "$group": {
"_id": "$userId",
"projectId": { "$push": "$projectId" }
}},
{ "$lookup": {
"from": "users",
"let": { "userId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$userId" ] }}},
{ "$project": { "firstName": 1 }}
],
"as": "user"
}},
{ "$unwind": "$user" },
{ "$lookup": {
"from": "projects",
"let": { "projectId": "$projectId" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$projectId" ] }}},
{ "$project": { "projectName": 1 }}
],
"as": "projects"
}}
])

Multiple sorting in aggregate with mongo

When I use a multiple sorting in aggregate method with mongo, results aren't sorting in the right way. This is my query :
db.MyCollection.aggregate(
{
"$unwind": "$objects"
},
{
"$lookup": {
"from": "CollectionA",
"localField": "objects.itemId",
"foreignField": "_id",
"as": "itemOne"
}
},
{
"$lookup": {
"from": "CollectionB",
"localField": "user_id",
"foreignField": "id",
"as": "users"
}
},
{
"$lookup": {
"from": "CollectionC",
"localField": "objects.itemName",
"foreignField": "name",
"as": "itemTwo"
}
},
{
"$addFields": {
"item": {
"$arrayElemAt": [
"$itemOne",
0
]
},
"user": {
"$arrayElemAt": [
"$users",
0
]
},
"itemP": {
"$arrayElemAt": [
"$itemTwo",
0
]
}
}
},
{
"$addFields": {
"itemName": {
"$ifNull": [
"$item.name",
"$objects.itemName"
]
},
"userName": {
"$concat": [
"$user.firstname",
" ",
"$user.lastname"
]
}
}
},
{
"$match": {
"client_id": 2
}
},
{
"$skip": 1
},
{
"$limit": 10
},
{
"$project": {
"date": "$objects.date",
"state": "$objects.state"
}
},
{
"$sort": {
"objects.state": 1,
"objects.date": 1,
}
}
)
To precise: "date" field is Date type and "state" field is number type.
If I use only one sort : result order is correct. But if I use 2 sorts, results are not order correctly. Have you got any ideas, why ?
As #Neil Lunn says :
They don't sort correctly because you renamed the fields in $project. So it should be { $sort: { state: 1, date: 1 } }