Mongo Aggregation Grouping and Mapping of Object Array - mongodb

I'm need to group a mongo collection and aggregate.
Example Collection
[{id:"1", skill: "cooking"},{id:"1", skill: "fishing"}]
Lookup Collection
[{ name: "cooking", value: 3 }, { name: "fishing", value: 2 }]
Desired Result
[{id: "1", skills: [{ value: 3, "cooking" }, { value: 2, "fishing"}]}]
Here's how far I am.
db.talent.aggregate([
{
$group: '$id'
skills: { $addToSet: '$skill' }
},
])
Result:
[{id: "1", skills: ["cooking", "fishing"]}]
I'm wondering if this is even possible.
I miss SQL, need help!

We can do this using $lookup, $group and $project in the aggregation pipeline
Shown below is the mongodb shell query
db.example_collection.aggregate([
{
$lookup: {
from: "lookup_collection",
localField: "skill",
foreignField: "name",
as: "skills"
}
},
{
$group: {
_id: "$id",
skills: {
$push: "$skills"
}
}
},
{
$project: {
"id": "$_id",
"skills.name": 1,
"skills.value": 1,
"_id": 0
}
}
])

Related

Mongo count for each document

I have a collection of elements which can have parent-child relation. It looks like this:
[
{
_id: 1,
parent_id: null
name: 'First element',
},
{
_id: 2,
parent_id: 1
name: 'Second element',
},
]
I want to calculate how many children each element have. I need to compare _id and parent_id of each document.
What I want to get:
[
{
_id: 1,
parent_id: null
name: 'First element',
childrens: 1,
}
{
_id: 2,
parent_id: 1
name: 'Second element',
childrens: 0,
}
]
How can I achieve that? I tried to look in documentation for aggregation and other stuff but still I don't know how to do that.
You can use $graphLookup to perform a recursive lookup.
db.collection.aggregate([
{
"$graphLookup": {
"from": "collection",
"startWith": "$_id",
"connectFromField": "_id",
"connectToField": "parent_id",
"as": "children"
}
},
{
"$addFields": {
"children": {
$size: "$children"
}
}
}
])
Here is the Mongo Playground for your reference.

How do I use mongo aggregation to lookup data in a subdocument?

I want to $lookup data from a subdocument in another collection. I have survey answers, and I want to group them by the question category's name.
The survey documents looks like this:
{
_id: new ObjectId("62555be60401f0a21553da9a"),
name: 'new survey',
questions: [
{
text: 'question 1',
category_id: new ObjectId("62555be60401f0a21553da99"),
options: [Array],
_id: new ObjectId("62555be60401f0a21553da9c"),
},
...
}
Category collection is just name and _id:
{
_id: new ObjectId("62555be60401r0a27553da99"),
name: "category name"
}
I have answer data like this:
[
{
answers: {
k: '62555be60401f0a21553da9c',
v: new ObjectId("62555880da8fb89651f6a292")
},
},
{
answers: {
k: '62555880da8fb89651f6a29b',
v: new ObjectId("62555880da8fb89651f6a29e")
},
}
...
]
k is a string that matches to the _id in the survey.questions array.
I'd like to get the resulting data like this:
[
{
answers: {
k: 'question 1',
v: new ObjectId("62555880da8fb89651f6a292")
},
category: 'category name'
},
{
answers: {
k: 'question 2',
v: new ObjectId("62555880da8fb89651f6a29e")
},
category: 'other category name'
}
...
]
any help would be greatly appreciated!
I think I could probably figure out the category part, but I cannot figure out how to use $lookup to get info from a subdocument. From the docs I'm guessing its maybe some pipeline within a lookup. Pretty stumped though.
You can do something like this, using a pipeline to match only surveys that have questions._id that matches the answer.k value
db.answer.aggregate([
{
$lookup: {
"from": "survey",
"let": {
"k": {
"$toObjectId": "$answers.k"
}
},
pipeline: [
{
"$match": {
"$expr": {"$in": ["$$k", "$questions._id"]}
}
}
],
as: "details"
}
},
{
$project: {
answers: 1,
details: {"$arrayElemAt": ["$details", 0]}
}
},
{
$project: {
answers: 1,
categoryData: {
$filter: {
input: "$details.questions",
as: "item",
cond: {$eq: ["$$item._id", {"$toObjectId": "$answers.k"}]}
}
}
}
},
{
$project: {
answers: 1,
catData: {"$arrayElemAt": ["$categoryData", 0]}
}
},
{
$lookup: {
from: "Category",
localField: "catData.category_id",
foreignField: "_id",
as: "cat"
}
},
{
$project: {
answers: 1,
_id: 0,
category: {"$arrayElemAt": ["$cat", 0]}
}
},
{
$project: {answers: 1, name: "$category.name"}
}
])
As you can see on the playground
Maybe it is possible to filter the results during the $lookup in order to simplify the rest of the query

perform lookup on array from another collection in MongoDB

I have a collection of Orders. each order has a list of Items, and each Item has catalog_id, which is an ObjectId pointing to the Catalogs collection.
I need an aggregate query that will retrieve certain orders - each order with its Items in extended fashion including the Catalog name and SKU. i.e:
Original data structure:
Orders: [{
_id : ObjectId('ord1'),
items : [{
catalog_id: ObjectId('xyz1'),
qty: 5
},
{
catalog_id: ObjectId('xyz2'),
qty: 3
}]
Catalogs: [{
_id : ObjectId('xyz1')
name: 'my catalog name',
SKU: 'XxYxZx1'
},{
_id : ObjectId('xyz2')
name: 'my other catalog name',
SKU: 'XxYxZx2'
}
]
ideal outcome would be:
Orders: [{
_id : ObjectId('ord1'),
items : [{
catalog_id: ObjectId('xyz1'),
catalog_name: 'my catalog name',
catalog_SKU: 'XxYxZx1' ,
qty: 5
},
{
catalog_id: ObjectId('xyz2'),
catalog_name: 'my other catalog name',
catalog_SKU: 'XxYxZx2' ,
qty: 3
}
]
What I did so far was:
db.orders.aggregate(
[
{
$match: {merchant_order_id: 'NIM333'}
},
{
$lookup: {
from: "catalogs",
//localField: 'items.catalog_id',
//foreignField: '_id',
let: { 'catalogId' : 'items.catalog_id' },
pipeline: [
{
$match : {$expr:{$eq:["$catalogs._id", "$$catalogId"]}}
},
{
$project: {"name": 1, "merchant_SKU": 1 }
}
],
as: "items_ex"
},
},
])
but items_ex comes out empty for some reason i cannot understand.
You need to first $unwind the items and reconstruct the array back using $group to match the exact position of qty with the catalogs_id inside the items array
db.orders.aggregate([
{ "$match": { "merchant_order_id": "NIM333" }},
{ "$unwind": "$items" },
{ "$lookup": {
"from": "catalogs",
"let": { "catalogId": "$items.catalog_id", "qty": "$items.qty" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$_id", "$$catalogId"] } }},
{ "$project": { "name": 1, "merchant_SKU": 1, "qty": "$$qty" }}
],
"as": "items"
}},
{ "$unwind": "$items" },
{ "$group": {
"_id": "$_id",
"items": { "$push": "$items" },
"data": { "$first": "$$ROOT" }
}},
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": ["$data", { "items": "$items" }]
}
}}
])
MongoPlayground
You're missing a dollar sign when you define your pipeline variable. There should be:
let: { 'catalogId' : '$items.catalog_id' },
and also this expression returns an array to you need $in instead of $eq:
{
$lookup: {
from: "catalogs",
let: { 'catalogId' : 'items.catalog_id' },
pipeline: [
{
$match : {$expr:{$in:["$_id", "$$catalogId"]}}
},
{
$project: {"name": 1, "merchant_SKU": 1 }
}
],
as: "items_ex"
}
}
Mongo Playground

MongoDB aggregate lookup with nested array

I have a complicated structure I am trying to "join".
The best way to describe it is that I have "Favorite Teams" stored with a user, as an array of name/IDs - however they are stored in a nested object. I want to return the users Favorite Teams Players WITH the team.
Here are the data models
PLAYERS
{
_id:
team_id:
name:
position:
}
TEAMS
{
_id:
name:
}
USER
{
_id:
name:
favs: {
mascots: [{
_id:
name:
}],
teams: [{
_id:
name:
}],
}
}
I have an array of Team IDs from the user.favs.teams - and what I want back is the players with their team name.
This is the current aggregation I am using - it is returning the players but not the teams...I am pretty sure I need to unwind, or similar.
players.aggregate([
{
$match: {
team_id: {
$in: [--array of team ID's--]
}
}
},
{
$lookup: {
from: 'teams',
localField: 'team_id',
foreignField: '_id',
as: 'players_team'
}
},
{
$project: {
_id: 1,
name: 1,
position: 1,
'players_team[0].name': 1
}
}
])
What I am getting back...
_id: 5c1b37b6fd15241940b11111
name:"Bob"
position:"Test"
team_id:5c1b37b6fd15241940b441dd
player_team:[
_id:5c1b37b6fd15241940b441dd
name:"Team A"
...other fields...
]
What I WANT to get back...
_id: 5c1b37b6fd15241940b11111
name:"Bob"
position:"Test"
team_id:5c1b37b6fd15241940b441dd
player_team: "Team A"
Use Below $lookup (Aggregation)
db.players.aggregate([
{
$lookup: {
from: "teams",
let: { teamId: "$team_id" },
pipeline: [
{
$match: { $expr: { $eq: [ "$_id", "$$teamId" ] } }
},
{
$project: { _id: 0 }
}
],
as: "players_team"
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{
"_id": "$_id",
"name": "$name",
"position": "$position",
"team_id": "$team_id"
},
{
player_team: { $arrayElemAt: [ "$players_team.name", 0 ] }
}
]
}
}
}
])
Sorry If your MongoDB version is less then 3.6. Because of new changes in MongoDB 3.6.

Aggregation filter and lookup on Mongodb

My first collection employeecategory is like below;
[{
name: "GARDENING"
},
{
name: "SECURITY"
},
{
name: "PLUMBER"
}
]
My second collection complaints is like below;
[{
communityId: 1001,
category: "SECURITY",
//other fields
}, {
communityId: 1001,
category: "GARDENING",
//other fields
}]
I am trying to join above tables and get the below result;
[{
"count": 1,
"name": "GARDENING"
}, {
"count": 1,
"name": "SECURITY"
}, {
"count": 0,
"name": "PLUMBER"
}]
Even if there are no records in collection 2 I need count. I tried below aggregation but didn't worked. If I removed match condition it is working but I need to filter on community id. Could some please suggest best way to do achieve this. Mongo DB version is 3.4.0
db.employeecategory.aggregate(
[{
$match: {
"complaints.communityId": 1001
}
}, {
"$lookup": {
from: "complaints",
localField: "name",
foreignField: "category",
as: "embeddedData"
}
}]
)
It was not possible to achieve both filtering for communityId = 1001 and grouping without losing count = 0 category in a single aggregation. The way to do it is first start from complaints collection, and filter the communityId = 1001 objects, and create a temp collection with it. Then from employeecategory collection, $lookup to join with that temp collection, and $group with name, you will have your result at this point, then drop the temp table.
// will not modify complaints document, will create a filtered temp document
db.complaints.aggregate(
[{
$match: {
communityId: 1001
}
},
{
$out: "temp"
}
]
);
// will return the answer that is requested by OP
db.employeecategory.aggregate(
[{
$lookup: {
from: "temp",
localField: "name",
foreignField: "category",
as: "array"
}
}, {
$group: {
_id: "$name",
count: {
$sum: {
$size: "$array"
}
}
}
}]
).pretty();
db.temp.drop(); // to get rid of this temporary collection
will result;
{ _id: "PLUMBER", count: 0},
{ _id: "SECURITY", count: 2},
{ _id: "GARDENING", count: 1}
for the test data I've had;
db.employeecategory.insertMany([
{ name: "GARDENING" },
{ name: "SECURITY" },
{ name: "PLUMBER" }
]);
db.complaints.insertMany([
{ category: "GARDENING", communityId: 1001 },
{ category: "SECURITY", communityId: 1001 },
{ category: "SECURITY", communityId: 1001 },
{ category: "SECURITY", communityId: 1002 }
]);