MongoDB Aggregation - How to condition for $set - node.js

I am unwinding a lookup joined by { preserveNullAndEmptyArrays: true } option to include documents whose sizes field is null, missing, or an empty array. But in my case, I need to join nested lookup and the output result is not as I expected.
Specifically, I am being to set a new buyer field by the $set operator from lookup but if this lookup is null or empty array it will return [ { } ] and I don't want that. How can I replace [ { } ] with empty array?
The result I got:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"buyers": [
{
"_id": ObjectId("5a934e000102030405000003"),
"buyer": {
"_id": ObjectId("5a934e000102030405000004"),
"display_name": "Phineas",
"user_id": "123"
},
"post_id": 1,
"user_id": "123"
}
],
"content": "content book 1",
"title": "title book 1"
},
{
"_id": ObjectId("5a934e000102030405000002"),
"buyers": [
{}
],
"content": "content book 3",
"title": "title book 3"
},
{
"_id": ObjectId("5a934e000102030405000001"),
"buyers": [
{}
],
"content": "content book 2",
"title": "title book 2"
}
]
The result that I expected:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"buyers": [
{
"_id": ObjectId("5a934e000102030405000003"),
"buyer": {
"_id": ObjectId("5a934e000102030405000004"),
"display_name": "Phineas",
"user_id": "123"
},
"post_id": 1,
"user_id": "123"
}
],
"content": "content book 1",
"title": "title book 1"
},
{
"_id": ObjectId("5a934e000102030405000002"),
"buyers": [],
"content": "content book 3",
"title": "title book 3"
},
{
"_id": ObjectId("5a934e000102030405000001"),
"buyers": [],
"content": "content book 2",
"title": "title book 2"
}
]
Now you can see my code in the Mongo playground link:
https://mongoplayground.net/p/qiC7FySejxF

Comparing your actual output and MongoDB Playground link, I believe that you miss out the $unset stage.
{
$unset: "buyers.buyers"
}
While after the $unset stage, you can add $set stage to update the buyers field by filtering the document which is not empty document via $filter.
{
$set: {
buyers: {
$filter: {
input: "$buyers",
cond: {
$ne: [
"$$this",
{}
]
}
}
}
}
}
Demo # Mongo Playground

Related

mongodb aggregate on array of objects

I'm trying to group my participants array in to such a way that for a single participant I should get all the meeting under that participant in an array.
[
{
"_id": "5fc73e7131e6a20f6c492178",
"participants": [
{
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 1",
"email": "participant1#gmail.com"
},
{
"_id": "5fc74e1254b8d337b4ae36d2",
"rsvp": "Not Answered",
"name": "Participant 2",
"email": "participant2#gmail.com"
}
],
"title": "Meeting 1",
"startTime": "2020-11-01T18:30:00.000Z",
"endTime": "2020-11-03T18:30:00.000Z"
},
{
"_id": "5fc73f1cdfc45d3ca0c84654",
"participants": [
{
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 2",
"email": "participant2#gmail.com"
}
],
"title": "Meeting 2",
"startTime": "2020-11-01T18:30:00.000Z",
"endTime": "2020-11-03T18:30:00.000Z"
}
]
my expected result should be like
[{
"participant": {
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 1",
"email": "participant1#gmail.com"
},
meetings: [{meeting1, meeting2, ...and so on}]
},
{
"participant": {
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 2",
"email": "participant2#gmail.com"
},
meetings: [{meeting2, meeting3, ...and so on}]
}
]
I'm kinda stuck for hours to figure it out. I tried the approach by using $group and $unwind but I was getting participants as an array consisting of a single participant(object). and on that I was unable to run $match to match according to the participant's email because participants field was an array.
I tried this
const docs = await Meeting.aggregate([
{ $unwind: '$participants' },
{
$lookup: {
from: 'participants',
localField: 'participants',
foreignField: '_id',
as: 'participants'
}
},
{ $match },
{ $group: { _id: "$participants", meetings: { $push: "$$ROOT" } } },
]);
but this is not matching the expected result which I want it to be.
You can unwind to deconstruct the array and use group to get your desired output
db.collection.aggregate([
{
"$unwind": "$participants"
},
{
$group: {
_id: "$participants._id",
participants: {
$first: "$participants"
},
meetings: {
"$addToSet": "$title"
}
}
}
])
Working Mongo playground

Mongoose: Update of array inside another nested array element not working as expected

Here is my mongo schema:
{
"_id": ObjectId("5f8af2fc5f23667adf3bbaf2"),
"score": 2.5,
"questions": [{
"_id": ObjectId("5f8af30d5f23667adf3bbaf5"),
"desc": "some text",
},
{
"_id": ObjectId("5f8af3115f23667adf3bbaf8"),
"desc": "some text",
"options": [{
"_id": ObjectId("5f8af3115f23667adf3bbaf9"),
"name": "some name",
"desc": "description 1"
},
{
"_id": ObjectId("5f8af3115f23667adf3bbafa"),
"name": "some name",
"desc": "description 2"
}
]
}
]
}
I've to update the name and desc of the option having id as 5f8af3115f23667adf3bbaf9 which is in the one of the array elements of the question attribute having id as 5f8af30d5f23667adf3bbaf5 which is again part of the data having id as 5f8af2fc5f23667adf3bbaf2
Tried the following query which is getting executed successfully but not updating the option:
Model.findOneAndUpdate({
_id : ObjectId("5f8af2fc5f23667adf3bbaf2"),
"questions._id": ObjectId("5f8af30d5f23667adf3bbaf5"),
"questions.options._id": ObjectId("5f8af3115f23667adf3bbaf9"),
}, {
$set: {
"questions.$[q].options.$[o].order": data.order,
"questions.$[q].options.$[o].definition": data.definition,
"questions.$[q].options.$[o].type": data.type,
},
},
{
arrayFilters: [{ "q._id": ObjectId(qid) }, { "o._id": ObjectId(oid) }]
})
Is tihs possible to do in a single mongoose findOneAndUpdate method?
Your query is correct, I have just hardcoded object id values in array filter and it is updating the documents. I have updated name and desc as u said. Do try this out. One more thing in mongoose u have to specify the object id as "mongoose.Types.ObjectId".
Therefore in your case it would be like "q._id": mongoose.Types.ObjectId("5f8af3115f23667adf3bbaf8").
And one more thing is that you are using findAndUpdate, try using update only depending on your mongoose version
Here is mongoplayground:
https://mongoplayground.net/p/TP5iCTAC5R_
Query:
db.collection.update({
_id: ObjectId("5f8af2fc5f23667adf3bbaf2"),
"questions._id": ObjectId("5f8af3115f23667adf3bbaf8"),
"questions.options._id": ObjectId("5f8af3115f23667adf3bbaf9")
},
{
$set: {
"questions.$[q].options.$[o].name": "anotherName",
"questions.$[q].options.$[o].desc": "anotherDesc"
}
},
{
arrayFilters: [
{
"q._id": ObjectId("5f8af3115f23667adf3bbaf8")
},
{
"o._id": ObjectId("5f8af3115f23667adf3bbaf9")
}
]
})
Output :
[
{
"_id": ObjectId("5f8af2fc5f23667adf3bbaf2"),
"questions": [
{
"_id": ObjectId("5f8af30d5f23667adf3bbaf5"),
"desc": "some text"
},
{
"_id": ObjectId("5f8af3115f23667adf3bbaf8"),
"desc": "some text",
"options": [
{
"_id": ObjectId("5f8af3115f23667adf3bbaf9"),
"desc": "anotherDesc",
"name": "anotherName"
},
{
"_id": ObjectId("5f8af3115f23667adf3bbafa"),
"desc": "description 2",
"name": "some name"
}
]
}
],
"score": 2.5
}
]

How can I update one document at nested array

{
"_id": "5e28b029a0c8263a8a56980a",
"name": "Recruiter",
"data": [
{
"_id": "5e28b0980f89ba3c0782828f",
"targetLink": "https://www.linkedin.com/in/dan-kelsall-7aa0926b/",
"name": "Dan Kelsall",
"headline": "Content Marketing & Copywriting",
"actions": [
{
"result": 1,
"name": "VISIT"
},
{
"result": 1,
"name": "FOLLOW"
}
]
},
{
"_id": "5e28b0980f89ba3c078283426f",
"targetLink": "https://www.linkedin.com/in/56wergwer/",
"name": "56wergwer",
"headline": "asdgawehethre",
"actions": [
{
"result": 1,
"name": "VISIT"
}
]
}
]
}
Here is one of my mongodb document. I'd like to update data->actions->result
So this is what I've done
Campaign.updateOne({
'data.targetLink': "https://www.linkedin.com/in/dan-kelsall-7aa0926b/",
'data.actions.name': "Follow"
}, {$set: {'data.$.actions.result': 0}})
But it seems not updating anything and even it can't find the document by this 'data.actions.name'
You need the positional filtered operator since the regular positional operator ($) can only be used for one level of nested arrays:
Campaign.updateOne(
{ "_id": "5e28b029a0c8263a8a56980a", "data.targetLink": "https://www.linkedin.com/in/dan-kelsall-7aa0926b/" },
{ $set: { "data.$.actions.$[action].result": 0 } },
{ arrayFilters: [ { "action.name": "Follow" } ] }
)

Extracting some fields from an object in mongodb

I am new to mongoDB and I am unable to figure out how to extract only few fields from an object in a collection.I have a collection named recipe which is as follow:-
[
{
"_id": "b44e864d-de8f-4110-b676-bbee13417b2e",
"title": "Fried Eggs",
"ingredients": [
{
"name": "Eggs",
"amount": "10"
},
{
"name": "Olive Oil",
"amount": "2 tbs"
}
],
"steps": [
"1. Heat the pan",
"2.Add Olive Oil",
"3. Add Eggs",
"4. Saute"
],
"comments": [
{
"_id": "8604e67c-2426-4742-bc91-25ee1c4064b5",
"poster": "Ron Swanson",
"comment": "Wrong instructions, Got stuck in Toaster"
},
{
"_id": "040412bc-9046-49ca-9a65-19151b32cd91",
"poster": "Tom Haverford",
"comment": "What are Eggs?"
},
{
"_id": "1034f082-b802-4382-be3e-ef50f07a530a",
"poster": "Andy Dwyer",
"comment": "What came firsst, Eggs or Chicken"
}
]
},
{
"_id": "da6f9798-6547-42f5-85ed-043efabeb196",
"title": "Boiled Eggs",
"ingredients": [
{
"name": "Eggs",
"amount": "5"
},
{
"name": "Water",
"amount": "3 Cups"
}
],
"steps": [
"1.Boil the Water",
"2.Add Eggs",
"3. Keepit for 5 mins",
"4. Let it Cool Down",
"5. Peel off the shell"
],
"comments": [
{
"_id": "cc569ebb-1307-499a-b9ee-7b24e557859f",
"poster": "Ron Swanson",
"comment": "Wrong instructions, Got stuck in Oven"
},
{
"_id": "4f02756a-6e1b-4bda-a928-b3c5e03b55d8",
"poster": "Leslie Knope",
"comment": "What are Eggs?"
},
{
"_id": "0f34a5cc-ebe3-41b5-8f0b-6d1a3c206e6b",
"poster": "Andy Dwyer",
"comment": "Can I remove the shell first and then boil?"
}
]
}]
I want to extract only the title and id of both the recipes when a given route is accessed. Currently,I am using this function, which return all the field of both the recipes.
var recipesCollection = db.collection("recipes");
exports.getAllRecipes = function() {
return recipesCollection.find({}).toArray();
};
What query should I use to extract just the title and id of the two recipes and not all the details?
As per my understanding you are trying to fetch specific fields form the mongo collection. You can achieve this just by using the projection.
var recipesCollection = db.collection("recipes");
exports.getAllRecipes = function() {
return recipesCollection.find({}, {"title": 1}).toArray();
};
For more information follow this:
https://docs.mongodb.com/v3.2/tutorial/project-fields-from-query-results/#return-the-specified-fields-and-the-id-field-only

How to perform a $text search on a 'joined' collection via $lookup?

i’m new at Mongo, using v3.2. I have 2 collections Parent & Child. I’d like to use Parent.aggregate and use $lookup to “join” Child then perform $text $search on a field in Child and a date-range seach on the parent. Is this possible...?
In line with the comments already given, it is true that you cannot perform a $text search on the results of a $lookupsince there would not be an available index at any stage other than the very first pipeline stage. And it is true that especially considering that you really want the "join" to occur based on the results from the "child" collection, then it would indeed be better to search on the "child" instead.
Which brings the obvious conclusion that in order to do this you perform the aggregation on the "child" collection with the initial $text query and then $lookup the "parent" instead of the other way around.
As a working example, and just using the core driver for demonstration purposes:
MongoClient.connect('mongodb://localhost/rlookup',function(err,db) {
if (err) throw err;
var Parent = db.collection('parents');
var Child = db.collection('children');
async.series(
[
// Cleanup
function(callback) {
async.each([Parent,Child],function(coll,callback) {
coll.deleteMany({},callback);
},callback);
},
// Create Index
function(callback) {
Child.createIndex({ "text": "text" },callback);
},
// Create Documents
function(callback) {
async.parallel(
[
function(callback) {
Parent.insertMany(
[
{ "_id": 1, "name": "Parent 1" },
{ "_id": 2, "name": "Parent 2" },
{ "_id": 3, "name": "Parent 3" }
],
callback
);
},
function(callback) {
Child.insertMany(
[
{
"_id": 1,
"parent": 1,
"text": "The little dog laughed to see such fun"
},
{
"_id": 2,
"parent": 1,
"text": "The quick brown fox jumped over the lazy dog"
},
{
"_id": 3,
"parent": 1,
"text": "The dish ran away with the spoon"
},
{
"_id": 4,
"parent": 2,
"text": "Miss muffet on here tuffet"
},
{
"_id": 5,
"parent": 3,
"text": "Lady is a fox"
},
{
"_id": 6,
"parent": 3,
"text": "Every dog has it's day"
}
],
callback
)
}
],
callback
);
},
// Aggregate with $text and $lookup
function(callback) {
Child.aggregate(
[
{ "$match": {
"$text": { "$search": "fox dog" }
}},
{ "$project": {
"parent": 1,
"text": 1,
"score": { "$meta": "textScore" }
}},
{ "$sort": { "score": { "$meta": "textScore" } } },
{ "$lookup": {
"from": "parents",
"localField": "parent",
"foreignField": "_id",
"as": "parent"
}},
{ "$unwind": "$parent" },
{ "$group": {
"_id": "$parent._id",
"name": { "$first": "$parent.name" },
"children": {
"$push": {
"_id": "$_id",
"text": "$text",
"score": "$score"
}
},
"score": { "$sum": "$score" }
}},
{ "$sort": { "score": -1 } }
],
function(err,result) {
console.log(JSON.stringify(result,undefined,2));
callback(err);
}
)
}
],
function(err) {
if (err) throw err;
db.close();
}
);
});
This results in the $text matches from the query on the Child populated within each Parent, as well as being ordered by "score":
[
{
"_id": 1,
"name": "Parent 1",
"children": [
{
"_id": 2,
"text": "The quick brown fox jumped over the lazy dog",
"score": 1.1666666666666667
},
{
"_id": 1,
"text": "The little dog laughed to see such fun",
"score": 0.6
}
],
"score": 1.7666666666666666
},
{
"_id": 3,
"name": "Parent 3",
"children": [
{
"_id": 5,
"text": "Lady is a fox",
"score": 0.75
},
{
"_id": 6,
"text": "Every dog has it's day",
"score": 0.6666666666666666
}
],
"score": 1.4166666666666665
}
]
This ultimately makes sense and will be a lot more efficient than querying from the "parent" to find all "children" in a $lookup and then "post filtering" with $match to remove any "children" that did not meet criteria, and then subsequently discarding the "parents" without any match.
The same case is true for mongoose style "referencing" where you included an "array" of "children" within the "parent" instead of recording on the child. So as long as the "localField" on the child ( _id in that case ) is the same type as defined within the array on the parent as "foriegnField" ( which is will be if it was working with .populate() anyway ) then you are still getting the matched "parent(s)" for each "child" in the $lookup result.
This all comes down to reversing your thinking and realizing that the $text results are the most important thing, and therefore "that" is the collection on which the operation needs to be initiated.
It's possible, but just do it the other way around.
Using mongoose style with list of referenced children in the parent
Just showing the reverse case for references on the parent as well as date filtering:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/rlookup');
var parentSchema = new Schema({
"_id": Number,
"name": String,
"date": Date,
"children": [{ "type": Number, "ref": "Child" }]
});
var childSchema = new Schema({
"_id": Number,
"text": { "type": String, "index": "text" }
},{ "autoIndex": false });
var Parent = mongoose.model("Parent",parentSchema),
Child = mongoose.model("Child",childSchema);
async.series(
[
function(callback) {
async.each([Parent,Child],function(model,callback) {
model.remove({},callback);
},callback);
},
function(callback) {
Child.ensureIndexes({ "background": false },callback);
},
function(callback) {
async.parallel(
[
function(callback) {
Parent.create([
{
"_id": 1,
"name": "Parent 1",
"date": new Date("2016-02-01"),
"children": [1,2]
},
{
"_id": 2,
"name": "Parent 2",
"date": new Date("2016-02-02"),
"children": [3,4]
},
{
"_id": 3,
"name": "Parent 3",
"date": new Date("2016-02-03"),
"children": [5,6]
},
{
"_id": 4,
"name": "Parent 4",
"date": new Date("2016-01-15"),
"children": [1,2,6]
}
],callback)
},
function(callback) {
Child.create([
{
"_id": 1,
"text": "The little dog laughed to see such fun"
},
{
"_id": 2,
"text": "The quick brown fox jumped over the lazy dog"
},
{
"_id": 3,
"text": "The dish ran awy with the spoon"
},
{
"_id": 4,
"text": "Miss muffet on her tuffet"
},
{
"_id": 5,
"text": "Lady is a fox"
},
{
"_id": 6,
"text": "Every dog has it's day"
}
],callback);
}
],
callback
);
},
function(callback) {
Child.aggregate(
[
{ "$match": {
"$text": { "$search": "fox dog" }
}},
{ "$project": {
"text": 1,
"score": { "$meta": "textScore" }
}},
{ "$sort": { "score": { "$meta": "textScore" } } },
{ "$lookup": {
"from": "parents",
"localField": "_id",
"foreignField": "children",
"as": "parent"
}},
{ "$project": {
"text": 1,
"score": 1,
"parent": {
"$filter": {
"input": "$parent",
"as": "parent",
"cond": {
"$and": [
{ "$gte": [ "$$parent.date", new Date("2016-02-01") ] },
{ "$lt": [ "$$parent.date", new Date("2016-03-01") ] }
]
}
}
}
}},
{ "$unwind": "$parent" },
{ "$group": {
"_id": "$parent._id",
"name": { "$first": "$parent.name" },
"date": { "$first": "$parent.date" },
"children": {
"$push": {
"_id": "$_id",
"text": "$text",
"score": "$score"
}
},
"score": { "$sum": "$score" }
}},
{ "$sort": { "score": -1 } }
],
function(err,result) {
console.log(JSON.stringify(result,undefined,2));
callback(err);
}
)
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
With the output:
[
{
"_id": 1,
"name": "Parent 1",
"date": "2016-02-01T00:00:00.000Z",
"children": [
{
"_id": 2,
"text": "The quick brown fox jumped over the lazy dog",
"score": 1.1666666666666667
},
{
"_id": 1,
"text": "The little dog laughed to see such fun",
"score": 0.6
}
],
"score": 1.7666666666666666
},
{
"_id": 3,
"name": "Parent 3",
"date": "2016-02-03T00:00:00.000Z",
"children": [
{
"_id": 5,
"text": "Lady is a fox",
"score": 0.75
},
{
"_id": 6,
"text": "Every dog has it's day",
"score": 0.6666666666666666
}
],
"score": 1.4166666666666665
}
]
Noting that the "Parent 4" which would otherwise of had the largest ranking is removed since the date does not fall in the query range applied with $filter.

Resources