MongoDB - How to access a newly created array field created with $lookup and aggregate function - mongodb

Assuming I have two collections:
courses:
[
{
_id: 1,
name: "Geometry",
teacher_id: 1
},
{
_id: 2,
name: "English",
teacher_id: 2
}
]
teachers:
[
{
_id: 1,
firstName: "John",
lastName: "Adams"
},
{
_id: 2,
firstName: "Mary",
lastName: "Jane"
}
]
Now I perform an aggregation on the two collections to create something similar to a join in SQL:
db.collection("courses").aggregate([
{
$lookup:{
from: "teachers",
localField: "teacher_id",
foreignField: "_id",
as: "teacher_info"
}
},
{
$match:{
//I want to perform a match or filter here on the teacher_info
}
}
]);
The $lookup and aggregation will return a list of documents that have a new teacher_info array field.
[
{
_id: 1,
name: "Geometry",
teacher_id: 1,
teacher_info: [
{
_id: 1,
firstName: "John",
lastName: "Adams"
},
]
},
{
_id: 2,
name: "English",
teacher_id: 1,
teacher_info: [
{
_id: 2,
firstName: "Mary",
lastName: "Jane"
},
]
}
]
I need to perform a match operation in the newly created teacher_info array field. For example, only keep the teacher that has the first name "John". How can I do so? Is that possible?

You can work with dot notation in your $match stage.
{
$match: {
"teacher_info.firstName": "John"
}
}
Demo # Mongo Playground

Related

Field combination in an array where another field is the same in MongoDB?

I want to find matches with the same gender and insert them into a new field array aka names but I am unable to solution using MongoDB. Or mongooese.
Input example:
db.students.insertMany([
{ id: 1, name: "Ryan", gender: "M" },
{ id: 2, name: "Joanna", gender: "F" },
{ id: 3, name: "Andy", gender: "M" },
{ id: 4, name: "Irina", gender: "F" }
]);
Desired output:
[
{ gender: "M", names: ["Ryan","Andy"]},
{ gender: "F", names: ["Joanna","Irina"]}
]
Note: the table has many records and I do not know those gender/name pairs in advance
I try this but no results. I don't know how I should write this query.
db.students.aggregate([
{
$group:{
names : {$push:"$name"},
}
},
{ "$match": { "gender": "$gender" } }
])
You did not specify how to group. Try this one:
db.students.aggregate([
{
$group: {
_id: "$gender",
names: { $push: "$name" }
}
},
{
$set: {
gender: "$_id",
_id: "$$REMOVE"
}
}
])

mongodb - Merge object arrays based on key

In a mongodb database, I have the following data:
// db.people
[
{
_id: ObjectId("..."),
id: 111111111,
name: "George",
relatedPeople: [{ id: 222222222, relation: "child" }],
// A bunch of other data I don't care about
},
{
_id: ObjectId("..."),
id: 222222222,
name: "Jacob",
relatedPeople: [{ id: 111111111, relation: "father" }],
// A bunch of other data I don't care about
},
{
_id: ObjectId("..."),
id: 333333333,
name: "some guy",
relatedPeople: [],
// A bunch of other data I don't care about
},
]
I would like to query the people, and select only the fields I've shown, but have extra data in relatedPeople (id + relation + name)
So the desired output would be:
[
{
_id: ObjectId("..."),
id: 111111111,
name: "George",
relatedPeople: [{ id: 222222222, relation: "child", name: "Jacob" }],
},
{
_id: ObjectId("..."),
id: 222222222,
name: "Jacob",
relatedPeople: [{ id: 111111111, relation: "father", name: "George" }],
},
{
_id: ObjectId("..."),
id: 333333333,
name: "some guy",
relatedPeople: [],
},
]
I can get something close, with this query:
db.people.aggregate([
// { $match: { /** ... */ }, },
{
$lookup: {
from: "people",
let: { relatedPeopleIds: "$relatedPeople.id" },
pipeline: [
{ $match: { $expr: { $in: ["$id", "$$relatedPeopleIds"] } } },
{
$project: {
id: 1,
name: 1,
},
},
],
as: "relatedPeople2",
},
},
{
$project: {
id: 1,
name: 1,
relatedPeople: 1,
relatedPeople2: 1,
}
}
]);
But the data is split between two fields. I want to merge each object in the arrays by their id, and place the result array in relatedPeople
I found this question, but that merge is done over a range and uses $arrayElementAt which I can't use
I also tried looking at this question, but I couldn't get the answer to work (Kept getting empty results)
You can add one step using $arrayElementAt with $indexOfArray:
db.people.aggregate([
// { $match: { /** ... */ }, },
{$project: {id: 1, name: 1, relatedPeople: 1}},
{$lookup: {
from: "people",
let: { relatedPeopleIds: "$relatedPeople.id" },
pipeline: [
{ $match: { $expr: { $in: ["$id", "$$relatedPeopleIds"] } } },
{
$project: {
id: 1,
name: 1,
},
},
],
as: "relatedPeople2",
},
},
{$set: {
relatedPeople: {$map: {
input: "$relatedPeople",
in: {$mergeObjects: [
"$$this",
{$arrayElemAt: [
"$relatedPeople2",
{$indexOfArray: ["$relatedPeople2.id", "$$this.id"]}
]}
]}
}}
}},
{$unset: "relatedPeople2"}
])
See how it works on the playground example

How can I perform nested "joins" (joining 3 or more collections) in a MongoDB aggregation pipeline?

Let's say we have 3 hypothetical collections in MongoDB: customers, orders, and orderItems.
Each customer has multiple orders, and each order has multiple order items.
Here's some sample data for these 3 collections:
customers
[
{
customer_id: 1,
name: "Jim Smith",
email: "jim.smith#example.com"
},
{
customer_id: 2,
name: "Bob Jones",
email: "bob.jones#example.com"
}
]
orders
[
{
order_id: 1,
customer_id: 1
},
{
order_id: 2,
customer_id: 1
}
]
orderItems
[
{
order_item_id: 1,
name: "Foo",
price: 4.99,
order_id: 1
},
{
order_item_id: 2,
name: "Bar",
price: 17.99,
order_id: 1
},
{
order_item_id: 3,
name: "baz",
price: 24.99,
order_id: 2
}
]
Desired Result
How can I write my aggregation pipeline so that the result returned looks something like this?
[
{
customer_id: 1,
name: "Jim Smith",
email: "jim.smith#example.com"
orders: [
{
order_id: 1,
items: [
{
name: "Foo",
price: 4.99
},
{
name: "Bar",
price: 17.99
}
]
},
{
order_id: 2,
items: [
{
name: "baz",
price: 24.99
}
]
}
]
},
{
customer_id: 2,
name: "Bob Jones",
email: "bob.jones#example.com"
orders: []
}
]
Do nested lookup using lookup with pipeline,
$lookup with orders collection,
let, define variable customer_id that is from main collection, to access this reference variable inside pipeline using $$ like $$customer_id,
pipeline can add pipeline stages same as we do in root level pipeline
$expr whenever we match internal fields it requires expression match condition, so $$customer_id is parent collection field that declared in let and $customer_id is child collection's/current collection's field
$lookup with orderitems collection
db.customers.aggregate([
{
$lookup: {
from: "orders",
let: { customer_id: "$customer_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$$customer_id", "$customer_id"] } } },
{
$lookup: {
from: "orderitems",
localField: "order_id",
foreignField: "order_id",
as: "items"
}
}
],
as: "orders"
}
}
])
Playground
Tip:
Several joins considered as bad practice in NoSQL, I would suggest if you could add your order items in orders collection as array, you can save one join process for orderitems, see improved version in playground

mongodb nested array element $lookup

I have two collections, orders and products. I like to join all the order.items[] to products collection to add more fields to the items[]
Sample Data:
orders
[{ _id: 1, items: [
{ product_id: 1, price: 1.99, qty: 2 },
{ product_id: 2, price: 3.99, qty: 5 } ]}]
products
[{ _id: 1, name: "Product 1" }, { _id: 2, name: "Product 2 }]
Expected output:
[{ _id: 1, items: [
{ product_id: 1, name: "Product 1", price: 1.99, qty: 2 },
{ product_id: 2, name: "Product 2",, price: 3.99, qty: 5 } ]}]
I have tried using $lookup and pipeline (mongodb 3.6) and not getting the name value or even the match is not working.
Thanks for a help!
This query will help you, sorry if I didn't use v3.6.
db.orders.aggregate([
{
$unwind: "$items"
},
{
$lookup:
{
from: "products",
localField: "items.product_id",
foreignField: "_id",
as: "tproduct"
}
},
{
$project:
{
"_id" : 1,
"items.product_id" : 1,
"items.name" : { $arrayElemAt: ["$tproduct.name", 0] },
"items.price" : 1,
"items.qty" : 1
}
},
{
$group :
{
_id : "$_id",
items: { $push: "$items" }
}
}
])
They are 4 stages that I will explain:
$unwind will create a single object for each element in the array.
$lookup will find the correct product, keep in mind that Product._id should be unique.
$project will format my documents and in items.name I'm taking the first element of the lookup sentence.
$group will use the _id to group and push each item into a new array.
I'm pretty sure there are cleaner and easier ways to write this, but this should work without problems.

MongoDB : create a view which is the union of several collections

I currently have a collection that i need to split in several smaller collections. Is there a way to make a View containing the union of all my smaller collections ?
According to the MongoDB Manual, i could use the $lookup operator in the pipeline, but it ends up being more like a "join" than an "union".
Here is an example of what i want to do :
Current collection :
{ _id: 1, name: "abc", country: "us" }
{ _id: 2, name: "def", country: "us" }
{ _id: 3, name: "123", country: "de" }
{ _id: 4, name: "456", country: "de" }
Splitting into :
Collection_US
{ _id: 1, name: "abc", country: "us" }
{ _id: 2, name: "def", country: "us" }
Collection_DE
{ _id: 3, name: "123", country: "de" }
{ _id: 4, name: "456", country: "de" }
And then, make a view :
View
{ _id: 1, name: "abc", country: "us" }
{ _id: 2, name: "def", country: "us" }
{ _id: 3, name: "123", country: "de" }
{ _id: 4, name: "456", country: "de" }
Is it possible to do this ?
This is the same modified of taminov's code.
db.createView('union_view', 'us', [
{
$facet: {
us: [
{$match: {}}
],
de: [
{$limit: 1},
{
$lookup: {
from: 'de',
localField: '__unexistingfield',
foreignField: '__unexistingfield',
as: '__col2'
}
},
{$unwind: '$__col2'},
{$replaceRoot: {newRoot: '$__col2'}}
]
},
},
{$project: {data: {$concatArrays: ['$us', '$de']}}},
{$unwind: '$data'},
{$replaceRoot: {newRoot: '$data'}}
])
its very hacky but will work for small collections. you may end up having to use a real collection if the collections are big.
db.createView('union_view', 'col1', [
{
$facet: {
col1: [
{ $match:{}}
],
col2: [
{ $limit:1},
{ $lookup:{
from: 'col2',
localField: '__unexistingfield',
foreignField: '__unexistingfield',
as: '__col2'
}},
{ $unwind:'$__col2'},
{ $replaceRoot: {newRoot: '$__col2'}}
]
},
},
{ $project: { filesFolders: {$setUnion: ['$files', '$folders']}}},
{ $unwind: '$filesFolders' },
{ $replaceRoot: {newRoot: '$filesFolders'}}
])