MongoDB how to $project (limit fields) from the $lookup remote collection? - database

I want to $lookup for a remote collection like SQL Join but with Mongo. And I don't want all of the keys from the remote document to be pulled to the origin collection - just some specific keys.
This is what I have tried:
[
{
$lookup: {
from: "tables",
localField: "type",
foreignField: "_id",
as: "type"
}
},
{
$unwind: "$type"
},
},
{
$project: {
"type.title": 1
}
}
]
However this prints only "type.title" and ignores all of other keys even from the origin document.
Is there any way to tell MongoDB to pull only specific fields from the remote collection?

You can use below aggregation with mongodb 3.6 and above
[
{ "$lookup": {
"from": "tables",
"let": { "type": "$type" },
"pipeline": [
{ "$addFields": { "owners": { "$cond": { "if": { "$ne": [ { "$type": "$owners" }, "array" ] }, "then": [], "else": "$owners" } } }},
{ "$match": { "$expr": { "$eq": ["$_id", "$$type"] }}},
{ "$project": { "title": 1 }}
],
"as": "type"
}},
{ "$unwind": "$type" }
]

Related

Join collection with array object field with another collection in MongoDB

I'm working in MongoDB and getting stuck at one aggregation case. Let me show you my collection.
First collection (data):
[
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal" : "7d44251a-b308-4deb-875a-33ef0a69fe2b",
"place": "Chennai"
},
{
"animal" : "fcfdd527-5885-48b0-a91f-03f72f78528f",
"place": "Kolkata"
}
]
}
]
Second collection (Animal):
[
{
"_id": "7d44251a-b308-4deb-875a-33ef0a69fe2b",
"name": "Dog"
},
{
"_id": "7d44251a-b308-4deb-875a-33ef0a69fe2b",
"name": "Cat"
}
]
I'm using this query:
db.data.aggregate([
{
"$lookup": {
"from": "animal",
"localField": "data.animal",
"foreignField": "_id",
"as": "doc"
}
},
{
"$unwind": "$doc"
},
{
"$project": {
"_id": 1,
"data.animal": "$doc.name",
"data.place": 1
}
}
])
and it result me this
[
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal": "Dog",
"place": "Chennai"
},
{
"animal": "Dog",
"place": "Kolkata"
}
]
},
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal": "Cat",
"place": "Chennai"
},
{
"animal": "Cat",
"place": "Kolkata"
}
]
}
]
Where I'm expecting like this
[
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal": "Dog",
"place": "Chennai"
},
{
"animal": "Cat",
"place": "Kolkata"
}
]
}
]
Mongo Playground
Also sharing this question in Mongo playgroud. Thanks in advance!!
Solution 1
$unset - Deconstruct the data array into multiple documents.
$lookup - Perform join with animal collection.
$project - Decorate the output document. For data.animal field, get the first value via $first.
$group - Group by _id. Push the data document into the data array.
db.data.aggregate([
{
"$unwind": "$data"
},
{
"$lookup": {
"from": "animal",
"localField": "data.animal",
"foreignField": "_id",
"as": "doc"
}
},
{
"$project": {
"_id": 1,
"data.animal": {
$first: "$doc.name"
},
"data.place": 1
}
},
{
$group: {
_id: "$_id",
data: {
$push: "$data"
}
}
}
])
Demo Solution 1 # Mongo Playground
Solution 2
$lookup
$set - Set data field.
2.1. $map - Iterate the data array and returns a new array.
2.1.1. $mergeObjects - Merge current iterated document with place field and the document from 2.1.1.1.
2.1.1.1. $first - Get the first document from the filtered doc arrays by matching the ids via $filter.
$unset - Remove _id and animals._id fields.
db.data.aggregate([
{
"$lookup": {
"from": "animal",
"localField": "data.animal",
"foreignField": "_id",
"as": "doc"
}
},
{
$set: {
data: {
$map: {
input: "$data",
as: "data",
in: {
$mergeObjects: [
{
place: "$$data.place"
},
{
$first: {
$filter: {
input: "$doc",
cond: {
$eq: [
"$$this._id",
"$$data.animal"
]
}
}
}
}
]
}
}
}
}
},
{
$unset: [
"doc",
"data._id"
]
}
])
Demo Solution 2 # Mongo Playground

Finding ID of mongo documents with duplicated elements in nested array

I would like to extract from the collection the IDs of documents that have duplicate IDs of "drives" objects that are nested in the array that is in "streetModel".
This is my typical document :
{
"_id": {
"$oid": "61375bec4fa522001b608568"
},
"name": "Streetz",
"statusDetail": {},
"streetModel": {
"_id": "3.7389-51.0566",
"name": "Kosheen - Darude - Swedish - Trynidad - Maui",
"countryCode": "DEN",
"drives": [{
"_id": -903500698,
"direction": "WEST"
}, {
"_id": 1915399546,
"direction": "EAST"
}, {
"_id": 1294835467,
"direction": "NORTH"
}, {
"_id": 1248969937,
"direction": "EAST"
}, {
"_id": 1248969937,
"direction": "EAST"
}, {
"_id": 1492411786,
"direction": "SOUTH"
}]
},
"createdAt": {
"$date": "2021-09-07T12:32:44.238Z"
}
}
In this particular document with the ID 61375bec4fa522001b608568, in "streetModel", in "drives" array I have got duplicated drives objects with id 1248969937.
I would like to create a query to the database that will return the ID of all documents with such a problem (duplicate "drives").
Right now I have got this:
db.streets.aggregate([
{
$unwind: "$streetModel"
},
{
$unwind: "$drives"
},
{
$group: {
_id: {
id: "$_id"
},
sum: {
$sum: 1
},
}
},
{
$match: {
sum: {
$gt: 1
}
}
},
{
$project: {
_id: "$_id._id",
duplicates: {
drives: "$_id"
}
}
}
])
but that's not it.
I try in many ways to rewrite this query, but unfortunately it doesn't work.
Query
unwind
group by document id + driverid
keep only those that had more than one time same driveid
replace-root is to make the document better looking, you could $project also instead
if you need any more stage i think you can add it, for examplpe to get the documents that have this problem project only the docid's
Test code here
db.collection.aggregate([
{
"$unwind": {
"path": "$streetModel.drives"
}
},
{
"$group": {
"_id": {
"docid": "$_id",
"driveid": "$streetModel.drives._id"
},
"duplicates": {
"$push": "$streetModel.drives.direction"
}
}
},
{
"$match": {
"$expr": {
"$gt": [
{
"$size": "$duplicates"
},
1
]
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
"$_id",
"$$ROOT"
]
}
}
},
{
"$project": {
"_id": 0
}
}
])

Filter based on values after join operation - MongoDB

I have two collections in the following format -
collection 1
{
"_id": "col1id1",
"name": "col1doc1",
"properties": [ "<_id1>", "<_id2>", "<_id3>"]
}
collection 2
{
"_id": "<_id1>",
"name": "doc1",
"boolean_field": false
}
{
"_id": "<_id2>",
"name": "doc2",
"boolean_field": true
}
{
"_id": "<_id3>",
"name": "doc3",
"boolean_field" : false
}
the desired output is -
{
"_id": "col1id1",
"name": "col1doc1",
"property_names": ["doc1", "doc3"]
}
The field proerties of document in collection1 has three IDs of documents in collection2 but the output after join operation should contain only those which have the boolean_field value as false. How can I perform this filter with join operation in MongoDB?
$lookup can be used along with $unwind to achieve this.
db.col1.aggregate([
{
"$unwind": "$properties"
},
{
"$lookup": {
from: "col2",
localField: "properties",
"foreignField": "_id",
"as": "property_names"
}
},
{
"$match": {
"property_names": {
"$elemMatch": {
"bool_field": false
}
}
}
},
{
"$unwind": "$property_names"
},
{
"$group": {
"_id": "$_id",
"properties": {
"$push": "$properties"
},
"property_names": {
"$push": "$property_names"
}
}
},
{
"$project": {
"_id": 1,
"name": 1,
"property_names": {
"name": 1
}
}
}
]);

How to do lookup with an array in mongoDB

today I started working with MongoDB. I have created two collections: Restaurant and OpeningHours. I have inserted data to the database with the following code:
db.OpeningHours.insert({
day: "Sunday",
from: "10.00am",
to: "16.00pm"
});
db.Restaurant.insert({
name: "Restaurant01",
openingHoursId:
[
{id: db.OpeningHours.find({day: "Sunday", from: "10.00am", to: "16.00pm"})[0]._id},
]
});
The restaurant contains an array of OpeningHours ids. I want to write a query with lookup so I get all the data from the Restaurant and the data for corresponding opening hours. Here is my code so far and if I run it I get an error: Command failed...
db.Restaurant.aggregate([
{
$unwind: "$openingHoursId",
$lookup:
{
from: "OpeningHours",
localField: "id",
foreignField: "_id",
as: "RestaurantHours"
}
}
])
The expected result I want is something like this:
{
"_id": ObjectId("5c43b6c8d0fa3ff24621f749"),
"name": "Restaurant01",
"openingHoursId":
[
{
"id": ObjectId("5c43b6c8d0fa3ff2462fg93e")
}
],
"RestaurantHours" :
[
{
"_id": ObjectId("5c43b6c8d0fa3ff2462fg93e"),
"day": "Sunday",
"from": "10.00am",
"to": "16.00pm"
}
]
}
Your localField should be openingHoursId.id not only id
db.Restaurant.aggregate([
{ "$unwind": "$openingHoursId" },
{ "$lookup": {
"from": "OpeningHours",
"localField": "openingHoursId.id",
"foreignField": "_id",
"as": "openingHoursId.RestaurantHours"
}},
{ "$unwind": "$openingHoursId.RestaurantHours" },
{ "$group": {
"_id": "$_id",
"name": { "$first": "name" },
"openingHoursId": { "$push": "openingHoursId" }
}}
])

Aggregate multiple collections based on student Id

I am trying to aggregate 2 collections in MongoDB based on a student's ID. One collection consists of student personal information, another one consists of the students logs. The issue is that the data is in array which is why I think my aggregation is not working. Any help will be appreciated.
student collection
{
"_id" : ObjectId("(Object ID here"),
"data" : [
{
"name" : "John",
"id" : 1
},
{
"name" : "Sandy",
"id" : 2
}
]
}
logs collection
{
"_id" : ObjectId("(Object ID here"),
"logs" : [
{
"studentId" : 1,
"activity" : "11112,334,123"
},
{
"studentId" : 2,
"activity" : "11112,334,123"
}
]
}
Here is what I have tried:
dbo.collection("student").aggregate([
{ "$lookup": {
"localField": "data.id",
"from": "logs",
"foreignField": "logs.studentId",
"as": "studentInfo"
}
}]).toArray(function(err, results) {
console.log(results);
});
Expected result:
studentinfo: {
id: 1,
name: "John",
activity" : "11112,334,123"
}
You can use below aggregation with mongodb 3.6
So basically your foreign field is an array you need to use $lookup with the pipeline to $unwind the foreign array inside the $lookup pipeline and to match the corresponding ids.
db.students.aggregate([
{ "$lookup": {
"from": "logs",
"let": { "dataId": "$data.id" },
"pipeline": [
{ "$unwind": "$logs" },
{ "$match": { "$expr": { "$in": ["$logs.studentId", "$$dataId"] }}},
{ "$replaceRoot": { "newRoot": "$logs" }}
],
"as": "students"
}}
])
or use this to merge both the arrays
db.students.aggregate([
{ "$lookup": {
"from": "logs",
"let": { "dataId": "$data.id" },
"pipeline": [
{ "$unwind": "$logs" },
{ "$match": { "$expr": { "$in": ["$logs.studentId", "$$dataId"] }}},
{ "$replaceRoot": { "newRoot": "$logs" }}
],
"as": "students"
}},
{ "$project": {
"students": {
"$map": {
"input": "$students",
"in": {
"studentId": "$$this.studentId",
"activity": "$$this.activity",
"name": { "$arrayElemAt": ["$data.name", { "$indexOfArray": ["$data.id", "$$this.studentId"] }]}
}
}
}
}}
])
Output
[
{
"students": [
{
"activity": "11112,334,123",
"name": "John",
"studentId": 1
},
{
"activity": "11112,334,123",
"name": "Sandy",
"studentId": 2
}
]
}
]

Resources