MongoDB Lookup Aggregation - mongodb

I have Parking collection like:
parking: {
_id: xxxxxx,
name: xxxxxx,
vehicles: [{vehicleId: xxxxx, bid: xxxxx}...]
}
and car collection:
car: {
_id: "xxxx",
attributes: [xxxxx],
color: "xxxx"
}
When I do Lookup Aggregation:
$lookup: {
from: "car",
localField: "vehicles.vehicleId",
foreignField: "_id",
as: "cars"
}
I get:
parking: {
_id: xxxxxx,
name: xxxxxx,
vehicles: [{vehicleId: xxxxx, bid: xxxxx}],
cars: [car1,car2...]
}
So I struggle with merging new cars array with objects in vehicles array that match id.
Can I somehow replace vehicleId with car document that match?
I tried this but group operation remove name field from parking
db.parking.aggregate([
{ "$unwind": "$vehicles" },
{ "$lookup": {
"from": "car",
"as": "vehicles.vehicle",
"localField": "vehicles.vehicleId",
"foreignField": "_id"
}},
{ "$unwind": "$vehicles.vehicle" },
{ "$group": {
"_id": "$_id",
"vehicles": { "$push": "$vehicles" }
}}
])

It's easier use $map operator by combining the $reduce operator.
Try this one:
db.parking.aggregate([
{
"$lookup": {
"from": "car",
"localField": "vehicles.vehicleId",
"foreignField": "_id",
"as": "cars"
}
},
{
$addFields: {
vehicles: {
$map: {
input: "$vehicles",
as: "veh",
in: {
bid: "$$veh.bid",
vehicleId: {
$reduce: {
input: "$cars",
initialValue: "$$veh.vehicleId",
in: {
$cond: [
{
$eq: [ "$$this._id", "$$veh.vehicleId" ]
},
"$$this",
"$$value"
]
}
}
}
}
}
},
cars: "$$REMOVE"
}
}
])
MongoPlayground | Replace vehicleId

Related

MongoDB: how to aggregate from multiple collections with same aggregation pipeline

I'm trying to get aggregations with same aggregation pipeline including $match and $group operations from multiple collections.
For example,
with a users collection and collections of questions, answers and comments where every document has authorId and created_at field,
db = [
'users': [{ _id: 123 }, { _id: 456} ],
'questions': [
{ authorId: ObjectId('123'), createdAt: ISODate('2022-09-01T00:00:00Z') },
{ authorId: ObjectId('456'), createdAt: ISODate('2022-09-05T00:00:00Z') },
],
'answers': [
{ authorId: ObjectId('123'), createdAt: ISODate('2022-09-05T08:00:00Z') },
{ authorId: ObjectId('456'), createdAt: ISODate('2022-09-01T08:00:00Z') },
],
'comments': [
{ authorId: ObjectId('123'), createdAt: ISODate('2022-09-01T16:00:00Z') },
{ authorId: ObjectId('456'), createdAt: ISODate('2022-09-05T16:00:00Z') },
],
]
I want to get counts of documents from each collections with created_at between a given range and grouped by authorId.
A desired aggregation result may look like below. The _ids here are ObjectIds of documents in users collection.
\\ match: { createdAt: { $gt: ISODate('2022-09-03T00:00:00Z) } }
[
{ _id: ObjectId('123'), questionCount: 0, answerCount: 1, commentCount: 0 },
{ _id: ObjectId('456'), questionCount: 1, answerCount: 0, commentCount: 1 }
]
Currently, I am running aggregation below for each collection, combining the results in the backend service. (I am using Spring Data MongoDB Reactive.) This seems very inefficient.
db.collection.aggregate([
{ $match: {
created_at: { $gt: ISODate('2022-09-03T00:00:00Z') }
}},
{ $group : {
_id: '$authorId',
count: {$sum: 1}
}}
])
How can I get the desired result with one aggregation?
I thought $unionWith or $lookup may help but I'm stuck here.
You can try something like this, using $lookup, here we join users, with all the three collections one-by-one, and then calculate the count:
db.users.aggregate([
{
"$lookup": {
"from": "questions",
"let": {
id: "$_id"
},
"pipeline": [
{
"$match": {
$expr: {
"$and": [
{
"$gt": [
"$createdAt",
ISODate("2022-09-03T00:00:00Z")
]
},
{
"$eq": [
"$$id",
"$authorId"
]
}
]
}
}
}
],
"as": "questions"
}
},
{
"$lookup": {
"from": "answers",
"let": {
id: "$_id"
},
"pipeline": [
{
"$match": {
$expr: {
"$and": [
{
"$gt": [
"$createdAt",
ISODate("2022-09-03T00:00:00Z")
]
},
{
"$eq": [
"$$id",
"$authorId"
]
}
]
}
}
}
],
"as": "answers"
}
},
{
"$lookup": {
"from": "comments",
"let": {
id: "$_id"
},
"pipeline": [
{
"$match": {
$expr: {
"$and": [
{
"$gt": [
"$createdAt",
ISODate("2022-09-03T00:00:00Z")
]
},
{
"$eq": [
"$$id",
"$authorId"
]
}
]
}
}
}
],
"as": "comments"
}
},
{
"$project": {
"questionCount": {
"$size": "$questions"
},
"answersCount": {
"$size": "$answers"
},
"commentsCount": {
"$size": "$comments"
}
}
}
])
Playground link. In the above query, we use pipelined form of $lookup, to perform join on some custom logic. Learn more about $lookup here.
Another way is this, perform normal lookup and then filter out the elements:
db.users.aggregate([
{
"$lookup": {
"from": "questions",
"localField": "_id",
"foreignField": "authorId",
"as": "questions"
}
},
{
"$lookup": {
"from": "answers",
"localField": "_id",
"foreignField": "authorId",
"as": "answers"
}
},
{
"$lookup": {
"from": "comments",
"localField": "_id",
"foreignField": "authorId",
"as": "comments"
}
},
{
"$project": {
questionCount: {
"$size": {
"$filter": {
"input": "$questions",
"as": "item",
"cond": {
"$gt": [
"$$item.createdAt",
ISODate("2022-09-03T00:00:00Z")
]
}
}
}
},
answerCount: {
"$size": {
"$filter": {
"input": "$answers",
"as": "item",
"cond": {
"$gt": [
"$$item.createdAt",
ISODate("2022-09-03T00:00:00Z")
]
}
}
}
},
commentsCount: {
"$size": {
"$filter": {
"input": "$comments",
"as": "item",
"cond": {
"$gt": [
"$$item.createdAt",
ISODate("2022-09-03T00:00:00Z")
]
}
}
}
}
}
}
])
Playground link.

MongoDB aggregate $lookup with _ID's from array

I have two collections in MongoDB: "carts" and another "products"
Carts:
[{
"_id": {
"$oid": "62af0fefebc0b42a875c7df1"
},
"uuid": "6ca05ae0-522a-4db3-b380-2d2330ee1e27",
"cartitems": [
"62a0b24680cc2891148daf7b",
"62a7339d91d01868921afa0a",
"62a72f7191d01868921afa08",
"62a7330291d01868921afa09"
],
"created": "2022-06-19T14:00:47.846958537+02:00[Europe/Brussels]",
"lastupdate": "2022-06-19T14:01:06.15165564+02:00[Europe/Brussels]"
},
{...},
{...}]
products:
[{
"_id": {
"$oid": "62a0b24680cc2891148daf7b"
},
"name": "Product1",
"created": "2022-06-11T09:41:54.461308647+02:00[Europe/Brussels]",
"category": "Workshops",
"pricein": "28900",
"lastupdate": "2022-06-17T16:09:53.385655474+02:00[Europe/Brussels]"
},
{...},
{...}]
I would like to use Aggregate:
db.carts.aggregate([
{ $match: { uuid: "6ca05ae0-522a-4db3-b380-2d2330ee1e27" } },
{
$lookup: {
from: "products",
localField: "cartitems",
foreignField: "_id",
as: "output"
}
}
])
This is not working because localField: "cartitems" should be converted:
"$addFields": {
"convertedIdStr": {
"$toString": "$cartitems"
}
But I don't manage to convert because cartitems is an array.
Any help would be great, Thanks a lot!
Use $lookup with pipeline. In $lookup pipeline stage, add $match stage by filtering the (converted to string) product's _id is in cartitems (array) variable.
db.carts.aggregate([
{
$match: {
uuid: "6ca05ae0-522a-4db3-b380-2d2330ee1e27"
}
},
{
$lookup: {
from: "products",
let: {
cartitems: "$cartitems"
},
pipeline: [
{
$match: {
$expr: {
$in: [
{
$toString: "$_id"
},
"$$cartitems"
]
}
}
}
],
as: "output"
}
}
])
Sample Mongo Playground

Mongodb lookup for array of ids with nested array of objects in other collection

I am new to mongo db i am trying to find a lookup for two collection
one collection is users which has tags like
{
_id: "fdkjkjs",
first_name: "",
last_name: "",
role: "admin",
tags:
[
{ _id: "tag_1_id", name: "Tag 1" },
{ _id: "tag_2_id", name: "Tag 2" },
{ _id: "tag_3_id", name: "Tag 3" },
{ _id: "tag_4_id", name: "Tag 4" }
]
}
and a post collection is as below
{
_id: "fdkjkjs",
title: "",
slug: "",
tags: ["tag_1_id", tag_3_id]
}
So I want to get all the tags in post list API with the names that are in users collection.
so result i wanted belike
[{
_id: "fdkjkjs",
title: "",
slug: "",
tags: ["tag_1_id", tag_3_id],
selectedTags: [
{ _id: "tag_1_id", name: "Tag 1" },
{ _id: "tag_3_id", name: "Tag 3" }
],
}]
Method 1
db.posts.aggregate([
{
"$lookup": {
"from": "users",
"localField": "tags",
"foreignField": "tags._id",
"as": "selectedTags"
}
},
{
"$set": {
"selectedTags": {
"$filter": {
"input": { "$first": "$selectedTags.tags" },
"as": "item",
"cond": { $in: [ "$$item._id", "$tags" ] }
}
}
}
}
])
mongoplayground
Method 2
db.posts.aggregate([
{
$lookup: {
from: "users",
let: { tags_post: "$tags" },
pipeline: [
{
"$unwind": "$tags"
},
{
$match: {
$expr: {
$in: [ "$tags._id", "$$tags_post" ]
}
}
},
{
"$replaceWith": "$tags"
}
],
as: "selectedTags"
}
}
])
mongoplayground
Method 3
db.posts.aggregate([
{
$lookup: {
from: "users",
localField: "tags",
foreignField: "tags._id",
let: { tags_post: "$tags" },
pipeline: [
{
"$unwind": "$tags"
},
{
$match: {
$expr: {
$in: [ "$tags._id", "$$tags_post" ]
}
}
},
{
"$replaceWith": "$tags"
}
],
as: "selectedTags"
}
}
])
mongoplayground
Method 2 arrange data first and then lookup, while method 3 lookup first and then arrange data. Though 2 and 3 looks similar, I think method 3 is faster than method 2.

How to do an aggregation and lookup on double nested array in MongoDB?

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"
}
}
])

How to perform lookup in aggregation when foreignField is in array?

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"
}
}
])