How to project an addFields attribute in MongoDB - 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"
}

Related

Querying a multi-nested array in MongoDb 3.4.2

MongoDB Version - 3.4.2
I'm trying to query within the Sitecore Analytics database, trying to retrieve all users that are associated with a given List Id.
The example dataset I have follows the default Sitecore Analytics setup:
"Tags" : {
"Entries" : {
"ContactLists" : {
"Values" : {
"0" : {
"Value" : "{1E2D1AB7-72A0-4FF7-906B-DCDC020B87D2}",
"DateTime" : ISODate("2020-10-23T17:38:13.891Z")
},
"1" : {
"Value" : "{28BECCD3-476B-4B1D-9A75-02E59EF21286}",
"DateTime" : ISODate("2018-04-18T14:22:41.763Z")
},
"2" : {
"Value" : "{2C2BB0C3-483D-490E-B93A-9155BFBBE5DC}",
"DateTime" : ISODate("2018-05-10T14:26:08.494Z")
},
"3" : {
"Value" : "{DBE480F6-E305-4B35-9E6D-CBED64F4E44F}",
"DateTime" : ISODate("2018-10-27T02:41:28.776Z")
},
}
}
}
},
I want to iterate through all the entries within Values without having to specify 0/1/2/3, avoiding the following:
db.getCollection('Contacts').find({"Tags.Entries.ContactLists.Values.1.Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"})
I've tried the following:
db.getCollection('Contacts').find({"Tags.Entries.ContactLists.Values": {$elemMatch : {"Value":"{28BECCD3-476B-4B1D-9A75-02E59EF21286}"}}})
db.getCollection('Contacts').find({'Tags' : {$elemMatch : {$all : ['{28BECCD3-476B-4B1D-9A75-02E59EF21286}']}}})
db.getCollection('Contacts').forEach(function (doc) {
for(var i in doc.Tags.Entries.ContactLists.Values)
{
doc.Tags.Entries.ContactLists.Values[i].Value = "{28BECCD3-476B-4B1D-9A75-02E59EF21286}";
}
})
And a few other variations which I cannot recall now. And none work.
Any ideas if this is possible or on how to do this?
I want the outcome to just show filter out the results showing only the entries containing the matching GUID
Many thanks!
Demo - https://mongoplayground.net/p/upgYxgzPwJQ
It can be done using aggregation pipeline
Use $objectToArray to convert array
Use $filter to filter the array
db.collection.aggregate([
{
$addFields: {
filteredValue: {
$filter: {
input: {
$objectToArray: "$Tags.Entries.ContactLists.Values"
},
as: "val",
cond: {
$eq: [ // filter condition
"$$val.v.Value",
"{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
]
}
}
}
}
}
])
Output -
[
{
"Tags": {
"Entries": {
"ContactLists": {
"Values": {
"0": {
"DateTime": ISODate("2020-10-23T17:38:13.891Z"),
"Value": "{1E2D1AB7-72A0-4FF7-906B-DCDC020B87D2}"
},
"1": {
"DateTime": ISODate("2018-04-18T14:22:41.763Z"),
"Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
},
"2": {
"DateTime": ISODate("2018-05-10T14:26:08.494Z"),
"Value": "{2C2BB0C3-483D-490E-B93A-9155BFBBE5DC}"
},
"3": {
"DateTime": ISODate("2018-10-27T02:41:28.776Z"),
"Value": "{DBE480F6-E305-4B35-9E6D-CBED64F4E44F}"
}
}
}
}
},
"_id": ObjectId("5a934e000102030405000000"),
"filteredValue": [
{
"k": "1",
"v": {
"DateTime": ISODate("2018-04-18T14:22:41.763Z"),
"Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
}
}
]
}
]
You can not use $elemMatch because Values is not array, but object. You can solve the problem with Aggregation Pipeline:
$addFields to add new field Values_Array that will be array representation of Values object.
$objectToArray to transform Values object to array
$match to find all documents that has requested value in new Values_Array field
$project to specify which properties to return from the result
db.getCollection('Contacts').aggregate([
{
"$addFields": {
"Values_Array": {
"$objectToArray": "$Tags.Entries.ContactLists.Values"
}
}
},
{
"$match": {
"Values_Array.v.Value": "{28BECCD3-476B-4B1D-9A75-02E59EF21286}"
}
},
{
"$project": {
"Tags": 1
}
}
])
Here is the working example: https://mongoplayground.net/p/2gY-vu3Qrvz

Finding out the common rows present in two collections in MongoDB based on some columns

I've got two nested collections like collection1 and collection2:
Collection1 is like:
_id : { name : xyz,
Rollnumber : 123}
Stats : [ { subject : Maths,
Examdate : 2020-08-24 },
{ Subject : English,
Examdate : 2020-08-23 } ]
Collection2 is having same structure but few extra rows
I've to delete the rows from collection2 which are present in collection1 based on three columns name, rollnumber and examdate, if any of the examdate is "2020-08-24".
I tried to find the common rows in both the tables using:
db.collection1.find()
foreach(function(mydoc){
var name = mydoc._id.name;
var rollnumber = mydoc._id.rollnumber;
Var examdate = ISODate("2020-08-24T00:00:00.000Z");
db.collection2.aggregate([
{
$match : { "$and":[
{"_id.name" : name},
{"_id.rollnumber" : rollnumber},
{"Stats.Updated" : examdate} ] }}]).forEach(printjson); });
The query runs fine but doesn't returns any output. Any reason why
May be this is what you are looking for. From question it is not quite clear that your Examdate contains time component or not. I am assuming that it only has date component. Based on that your document should look something like below.
[
{
"_id": {
"name": "xyz",
"Rollnumber": 123
},
"Stats": [
{
"subject": "Maths",
"Examdate": {
"$date": "2020-08-23T00:00:00.000Z"
}
},
{
"Subject": "English",
"Examdate": {
"$date": "2020-08-24T00:00:00.000Z"
}
}
]
}
]
Notice this "$date": "2020-08-24T00:00:00.000Z" , time component is absent. If this is the case with your documents than the solution should work. If you have time component in Examdate field then let me know in comments.
Query to get document :-
db.collection.aggregate([
{
"$match": {
"_id.name": "xyz",
"_id.Rollnumber": 123,
"Stats.Examdate": ISODate("2020-08-24")
}
}
])
Try it on MongoPlayground
I am keeping the previous answer as it is. It would help people getting the context.
This is from the question.
db.collection1.find()
foreach(function(mydoc){
var name = mydoc._id.name;
var rollnumber = mydoc._id.rollnumber;
//instead of this iterate mydoc.Stats to find max date.
//Var examdate = ISODate("2020-08-24T00:00:00.000Z");
mydoc.Stats.forEach(function(dateDocs){
//code to find max date
})
db.collection2.aggregate([
{
$match : { "$and":[
{"_id.name" : name},
{"_id.rollnumber" : rollnumber},
{"Stats.Updated" : examdate} ] }}]).forEach(printjson); });
Here is the updated query for dates with time component.
db.collection.aggregate([
{
"$match": {
"_id.name": "xyz",
"_id.Rollnumber": 123
}
},
{
"$unwind": "$Stats"
},
{
$addFields: {
"Stats.UpdatedString": {
$dateToString: {
format: "%Y-%m-%d",
date: "$Stats.Updated"
}
}
}
},
{
"$group": {
"_id": {
"name": "$_id.name",
"Rollnumber": "$_id.Rollnumber"
},
"Stats": {
"$addToSet": "$Stats"
}
}
},
{
"$match": {
"Stats.UpdatedString": "2020-08-24"
}
},
{
"$project": {
"Stats.UpdatedString": 0
}
}
])
Try it here

mongodb - find previous and next document in aggregation framework

After applying a long pipeline to my collection I can obtain something like this:
{
{
"_id": "main1",
"title": "First",
"code": "C1",
"subDoc": {
"active": true,
"sub_id": "main1sub1",
"order": 1
}
},
{
"_id": "main2",
"title": "Second",
"code": "C2",
"subDoc": {
"active": true,
"sub_id": "main2sub1",
"order": 1
}
},
{
"_id": "main3",
"title": "Third",
"code": "C3",
"subDoc": {
"active": false,
"sub_id": "main3sub1",
"order": 1
}
}
}
The documents are already in the correct order. Now I have to find the document immediately preceding or following the one corresponding to a given parameter. For example, if I know { "code" : "C2" } I have to retrieve the previous document (example document with "code" : "C1").
I only need to get that document, not the others.
I know how to do it using the find () method and applying sort () and limit () in sequence, but I want to get the document directly in the aggregation pipeline, adding the necessary stages to do it.
I've tried some combinations of $ indexOfArray and $ arrayElemAt, but the first problem I encounter is that I don't have an array, it's just documents.
The second problem is that the parameter I know might sometimes be inside the subdocument, for example {"sub_id": "main3sub1"}, and again I should always get the previous or next parent document as a response (in the example, the pipeline should return document "main2" as previous document)
I inserted the collection in mongoplayground to be able to perform the tests quickly:
mongoplayground
Any idea?
If you want to retrieve only the previous document, use the following query:
First Approach:
Using $match,$sort,$limit
db.collection.aggregate([
{
$match: {
code: {
"$lt": "C2"
}
}
},
{
"$sort": {
code: -1
}
},
{
$limit: 1
}
])
MongoDB Playground
Second Approach:
As specified by # Wernfried Domscheit,
Converting to array and then using $arrayElemAt
db.collection.aggregate([
{
$group: {
_id: null,
data: {
$push: "$$ROOT"
}
}
},
{
$addFields: {
"index": {
$subtract: [
{
$indexOfArray: [
"$data.code",
"C2"
]
},
1
]
}
}
},
{
$project: {
_id: 0,
data: {
$arrayElemAt: [
"$data",
"$index"
]
}
}
},
{
$replaceRoot: {
newRoot: "$data"
}
}
])
MongoDB Playground

MongoDB: I have an array with documents containing an ID. How can I fill the rest of the document with the aggregation framework?

I have a document which has an array bundleArticles. The array itself holds a list of articles. At first the article contains just the ID.
How can I fill up the rest of the article by using the aggregation framework?
First step would be to look up the articles with a lookup query but then I have an object containing all 4 articles.
How do I fill up the original array with the correct articles?
Here's an input document:
I would like the bundleArticles.x.article property to be filled up with the details of that article. So shortdescription etc are not null but the values of that article.
I have this aggreagate right now:
This is a step in the right direction but I now end up with only having an _id and the bundleArticles Property due to the group statement.
db['products.articles.r092'].aggregate([
{ "$match" : { "bundleArticles": { "$gt": [] } } },
{ "$unwind": "$bundleArticles" },
{ "$lookup" : { "from" : "products.articles.r092", "localField" : "bundleArticles.article.id", "foreignField" : "_id", "as" : "bundleArticles.tempArticle" } },
{ "$unwind": "$bundleArticles.tempArticle" },
{ "$addFields": {
"bundleArticles.article": {
"id": "$bundleArticles.tempArticle._id",
"identifiers": "$bundleArticles.tempArticle.identifiers",
"type": "$bundleArticles.tempArticle.type",
"slug": "$bundleArticles.tempArticle.slug",
"name": "$bundleArticles.tempArticle.name",
"shortDescription": "$bundleArticles.tempArticle.shortDescription",
"imageUrl": "$bundleArticles.tempArticle.imageUrl",
"properties": "$bundleArticles.tempArticle.properties",
"price": "$bundleArticles.tempArticle.price",
"discount": "$bundleArticles.tempArticle.discount",
"parentCategorySlugs": "$bundleArticles.tempArticle.parentCategorySlugs",
"variations": "$bundleArticles.tempArticle.variations"
}
}
},
{
"$project": {
"bundleArticles.tempArticle": 0
}
}
,
{
"$group": {
_id: "$_id",
bundleArticles: {
"$push": "$bundleArticles"
}
}
}
])

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 }