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"
}}
])
Related
I have three collections named issue, category and article, they are defined as follows:
// issue
{
"_id": "612775fbd237d9769a9fc3e4",
"title": "Weekly Issue1",
"data": [
{
"categoryId": ObjectId("61272e7dd237d9769a9fc3d9"),
"articles": [ ObjectId("61272f29d237d9769a9fc3da"), ...]
},
...
]
}
]
}
// category
{
"_id" : "61272e7dd237d9769a9fc3d9",
"name" : "News"
}
// article
{
"_id" : "61272f29d237d9769a9fc3da",
"title" : "Some News",
"url" : "https://www.google.com"
}
I would like to do an aggregation on issue and hope to get JSON results like this:
{
"id": "612775fbd237d9769a9fc3e4",
"tittle": "Weekly Issue1",
"data": [
{
"categoryId": "61272e7dd237d9769a9fc3d9",
"categoryName": "News"
"articles": [ {id: "61272f29d237d9769a9fc3da", title:"Some News", url:"https://www.google.com"}, ...]
},
...
]
}
]
}
My question is how to write the aggregation script? I am just a newbie to MongoDB, I have no idea about it now.
db.issue.aggregate([])
Thanks :-)
$unwind deconstruct data array
$lookup with article collection
$lookup with category collection
$addFields to edit categoryName field, $arrayElemAt to get first element from result of category
$group by _id and reconstruct the data array and return required fields by $first operator
db.issue.aggregate([
{ $unwind: "$data" },
{
$lookup: {
from: "article", // replace your original collection name
localField: "data.articles",
foreignField: "_id",
as: "data.articles"
}
},
{
$lookup: {
from: "category", // replace your original collection name
localField: "data.categoryId",
foreignField: "_id",
as: "data.categoryName"
}
},
{
$addFields: {
"data.categoryName": { $arrayElemAt: ["$data.categoryName.name", 0] }
}
},
{
$group: {
_id: "$_id",
title: { $first: "$title" },
data: { $push: "$data" }
}
}
])
Playground
use this aggregate
I think you could use $project instead of $addField but in test, I got some issue, and use addFields
[
{
'$lookup': {
'from': 'category',
'localField': 'data.0.categoryId',
'foreignField': '_id',
'as': 'dataCategory'
}
}, {
'$lookup': {
'from': 'article',
'localField': 'data.0.articles',
'foreignField': '_id',
'as': 'dataArticles'
}
}, {
'$unwind': {
'path': '$dataCategory'
}
}, {
'$addFields': {
'data': [
{
'categoryId': '$dataCategory._id',
'categoryName': '$dataCategory.name',
'articles': '$dataArticles'
}
]
}
}, {
'$project': {
'data': 1,
'tittle': 1
}
}
]
Because you have a nested array, you can do also a nested $lookup
with the first $lookup we add the category information
with the second nested $lookup we add the articles info
(the middle $addFields is to keep from the joined-category only its own articles)
Test code here
db.issue.aggregate([
{
"$lookup": {
"from": "category",
"let": {"data": "$data"},
"pipeline": [
{"$match": {"$expr": {"$in": ["$_id","$$data.categoryId"]}}},
{
"$addFields": {
"articles": {
"$arrayElemAt": [
{
"$filter": {
"input": "$$data",
"as": "d",
"cond": {"$eq": ["$$d.categoryId","$_id"]
}
}
},0]}}
},
{
"$lookup": {
"from": "article",
"localField": "articles.articles",
"foreignField": "_id",
"as": "articles"
}
}
],
"as": "data"
}
}
])
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.
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.
I have two collections:
// users
{
_id: "5cc7c8773861275845167f7a",
name: "John",
accounts: [
{
"_id": "5cc7c8773861275845167f76",
"name": "Name1",
},
{
"_id": "5cc7c8773861275845167f77",
"name": "Name2",
}
]
}
// transactions
{
"_id": "5cc7c8773861275845167f75",
"_account": "5cc7c8773861275845167f76",
}
Using lookup I want to populate _account field in transactions collection with respective element from users.accounts array.
So, I want the final result as:
{
"_id": "5cc7c8773861275845167f75",
"_account": {
"_id": "5cc7c8773861275845167f76",
"name": "Name1",
},
}
I have already tried using this code:
db.transactions.aggregate([
{
$lookup:
{
from: "users.accounts",
localField: "_account",
foreignField: "_id",
as: "account"
}
}
])
In the result account array comes as empty.
What is the correct way to do it ?
You can use below aggregation with mongodb 3.6 and above
db.transactions.aggregate([
{ "$lookup": {
"from": "users",
"let": { "account": "$_account" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$$account", "$accounts._id"] } } },
{ "$unwind": "$accounts" },
{ "$match": { "$expr": { "$eq": ["$$account", "$accounts._id"] } } }
],
"as": "_account"
}},
{ '$unwind': '$_account' }
])
Try with this
I think case 1 is better.
1)-
db.getCollection('transactions').aggregate([
{
$lookup:{
from:"user",
localField:"_account",
foreignField:"accounts._id",
as:"trans"
}
},
{
$unwind:{
path:"$trans",
preserveNullAndEmptyArrays:true
}
},
{
$unwind:{
path:"$trans.accounts",
preserveNullAndEmptyArrays:true
}
},
{$match: {$expr: {$eq: ["$trans.accounts._id", "$_account"]}}},
{$project:{
_id:"$_id",
_account:"$trans.accounts"
}}
])
2)-
db.getCollection('users').aggregate([
{
$unwind:{
path:"$accounts",
preserveNullAndEmptyArrays:true
}
},
{
$lookup:
{
from: "transactions",
localField: "accounts._id",
foreignField: "_account",
as: "trans"
}
},
{$unwind:"$trans"},
{
$project:{
_id:"$trans._id",
_account:"$accounts"
}
}
])
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.