How to query subdocuments using $lookup in mongodb? - mongodb

I have a personal project and I am new in MongoDB aggregate function.
I got the correct result querying two collections using $lookup but I want to modify the result and get my desired output.
Here is my Sample Collection
"users": [
{
"_id" : "60499e72b60a8819c4e0fa03"
"LastName": "Doe"
"FirstName" "John"
}
]
"userdocuments": [
{
"_id": "61025b9f890bacbe8f450f6a",
"userid": 60499e72b60a8819c4e0fa03,
documents: {
documentOne: {
documentTitle: "This is Document One"
},
documentTwo: {
documentTitle: "This is Document Two"
},
documentThree: {
documentTitle: "This is Document Three"
},
}
}
]
I'm getting a correct result like this using $lookup and $unwind
https://mongoplayground.net/p/k1lmvJg-tB7
[
{
"FirstName": "John",
"LastName": "Doe",
"_id": "60499e72b60a8819c4e0fa03",
"documents": {
"_id": "61025b9f890bacbe8f450f6a",
"userid": "60499e72b60a8819c4e0fa03"
"documents": {
"documentOne": {
"documentTitle": "This is Document One"
},
"documentThree": {
"documentTitle": "This is Document Three"
},
"documentTwo": {
"documentTitle": "This is Document Two"
}
},
}
}
]
But I want my output like this. And I want to project userid and _id so I can get my desired output. Thanks you so much for helping
[
{
"FirstName": "John",
"LastName": "Doe",
"_id": "60499e72b60a8819c4e0fa03",
"documents": {
"documentOne": {
"documentTitle": "This is Document One"
},
"documentThree": {
"documentTitle": "This is Document Three"
},
"documentTwo": {
"documentTitle": "This is Document Two"
}
},
}
}
]

You can select first element from documents array by $arrayElemAt or $first(v4.4) operators after lookup stage,
{
$addFields: {
Documents: {
$arrayElemAt: ["$Documents.documents", 0]
}
}
}
Playground

Related

filtering MongoDB array of Nested objects

I'm using MongoDB Compass for my queries while searching through a lot of data that I've inherited and quite often being asked to produce reports on the data for various teams but the documents often have too much data for them to easily parse so I'd like to cut down the data being reported on as much as possible
I've got the following example document
{
"_id": "123456",
"name": "Bob",
"date": "2022-07-01",
"fruit": [
{
"_id": "000001",
"foodName": "apple",
"colour": "red"
},
{
"_id": "000002",
"foodName": "apple",
"colour": "green"
},
{
"_id": "000003",
"foodName": "banana",
"colour": "yellow"
},
{
"_id": "000004",
"foodName": "orange",
"colour": "orange"
}
]
}
using
db.people.find( { "fruit.foodName" : "apple" } )
returns the whole document
I'd like to search for just the apples so that I get the result:
{
"_id": "123456",
"name": "Bob",
"date": "2022-07-01",
"fruit": [
{
"_id": "000001",
"foodName": "apple",
"colour": "red"
},
{
"_id": "000002",
"foodName": "apple",
"colour": "green"
}
]
}
Is that possible?
You will need to use an aggregation for this and use the $filter operator, The reason you can't use the query language for this is because their projection options are limited and only allow the projection of a single array element, because in your case the array can contain more than 1 matching subdocument it won't do.
You can read more about query language projections here
db.collection.aggregate([
{
$match: {
"fruit.foodName": "apple"
}
},
{
$addFields: {
fruit: {
$filter: {
input: "$fruit",
cond: {
$eq: [
"$$this.foodName",
"apple"
]
}
}
}
}
}
])
Mongo Playground

MongoDb regex search in array objects

I have the following collection:
{
"invoice": {
"data": [{
"name": "VOUCHERNUMBER",
"value": "59302311"
}, {
"name": "VOUCHERDATE",
"value": "2020-02-20"
}
]
}
},
{
"invoice": {
"data": [{
"name": "VOUCHERNUMBER",
"value": "59112389"
}, {
"name": "VOUCHERDATE",
"value": "2020-02-20"
}
]
}
},
{
"invoice": {
"data": [{
"name": "VOUCHERNUMBER",
"value": "59302378"
}, {
"name": "VOUCHERDATE",
"value": "2020-02-11"
}
]
}
}
My task is to build a query that find all invoices which invoicenumbers includes "11" (or any other substring).
So I built the following statement:
{"invoice.data.name": "VOUCHERNUMBER", "invoice.data.value": {$regex : "11"} }
I'm expecting a result of the first two objects, but because of the second value in the third object, mongodb returns me all three objects. Then I tried
{$and : [{"invoice.data.name": "VOUCHERNUMBER"}, {"invoice.data.value": {$regex : "11"}}]}
with the same result ...
So I'm running out of ideas. Is there a solution to search for the string only in the value field where the corresponding "name" field contains "VOUCHERNUMBER"?
You need $elemMatch.
The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
db.collection.find({
"invoice.data": {
"$elemMatch": {
"name": "VOUCHERNUMBER",
"value": {
$regex: "11"
}
}
}
})
Sample Mongo Playground

How to project an addFields attribute in MongoDB

I have a document in my collection testdb that I want to match to and looks like:
{
"student": {
"first": "Joe",
"last": "Johnson"
},
"semester": [{
"semesterName": "Spring2021",
"courses": [{
"title": "Calculus 1",
"professor": "Erik Paulson",
"TA": "Paul Matthews"
},
{
"title": "Computer Science 1",
"professor": "Dennis Ritchie",
"TA": "Ken Thompson"
}
]
}]
}
I want to match on the title attribute in the courses array and return the professor attribute without all of its nesting.
So I have the following query:
db.testcol.aggregate([
{ $match: { "semester.courses.title" : "Calculus 1" } },
{ $project: { "professor" : 1, "_id" : 0 } },
{ $addFields: { "professor": "$semester.courses.professor" } },
]);
but I seem to be getting an output of just { } when I want an output of { "professor" : "Erik Paulson" }.
Can somebody explain why this is happening and how I can fix it? My logic is that I am using $addFields to set the new professor attribute to be the professor attribute inside of the array of course objects whenever there is a match to the desired course title. I am then using $project to return only the new attribute.
Explanation:
Mistake is in your $project stage. property professor is two level inside the document which must be referenced as semester.courses.professor. Accessing it as professor will result in empty value.
So you can fix it using below query. Try this:
db.testcol.aggregate([
{ $unwind: "$semester" },
{ $unwind: "$semester.courses" },
{ $match: { "semester.courses.title" : "Calculus 1" } },
{
$project: {
"_id": 0,
"professor": "$semester.courses.professor"
}
}
]);
Output:
{
"professor" : "Erik Paulson"
}

MongoDb aggregation framework to group elements of inner array

I'm using MongoDB aggregation framework trying to transform each document:
{
"all": [
{
"type": "A",
"id": "1"
},
{
"type": "A",
"id": "1"
},
{
"type": "B",
"id": "2"
},
{
"type": "A",
"id": "3"
}
]
}
into this:
{
"unique_type_A": [ "3", "1" ]
}
(final result is a collection of n documents with unique_type_A field)
The calculation consists of returning in an array all the uniques types of entities of type A.
I got stuck with $group step, anyone knows how to do it?
To apply this logic to each document, you can use the following;
db.collection.aggregate([
{
$unwind: "$all"
},
{
$match: {
"all.type": "A"
}
},
{
$group: {
_id: {
"type": "$all.type",
"oldId": "$_id"
},
unique_type_A: {
$addToSet: "$all.id"
}
}
},
{
$project: {
_id: 0
}
}
])
Where we first $unwind, to be able to filter and play with each member of all array. Then we just filter the non type:"A" members. The $group stage has the difference with a complex _id, where we utilize the _id of $unwind result, which refers back to the original document, so that we can group the results per original document. Collecting the id from all array with $addToSet to keep only unique values, and voilĂ !
And here is the result per document;
[
{
"unique_type_A": [
"3",
"1"
]
},
{
"unique_type_A": [
"4",
"11",
"5"
]
}
]
Check the code interactively on Mongoplayground

Unwind embedded document in mongodb

I have documents like below.
{
"_id": {
"$oid": "526fdc1fd6b0a8182300009c"
},
"body": "test abc",
"emb" : [{"body":"text","em":"abc.com","auth":"XYZ"},
{"body":"text","em":"abc.com","auth":"ABC"}
]
}
{
"_id": {
"$oid": "526fdc1fd6b0a8182300009d"
},
"body": "test abc",
"emb" : [{"body":"text","em":"abc.com","auth":"PQR"},
{"body":"text","em":"abc.com","auth":"ABC"}
]
}
If I want to count occurrences of each "auth" in the inner array of documents, how can I do that? The result I am expecting is
"ABC":2
"PQR":1
"XYZ":1
$unwind the emb array with {$unwind: "$emb"}
group by emb.auth while counting with {$group: { _id: "$emb.auth", count: { $sum:1 } } }
This gives you the information you want, although in a slightly different syntax:
{ _id:"ABC", count:2 },
{ _id:"PQR", count:1 },
{ _id:"XYZ", count:1 }