Mongo aggregation: add field derived from another field - mongodb

I have documents of the following form present in a collection:
{
name: "Michael",
likes: [{ name: "reading", amount: 80}, {name: "eating", amount: 70}]
}
I want to add a new field to all the documents which will be 0/1 depending on whether the person likes reading. I have to check for the presence of likes.name = "reading".
I tried adding the following stage in my aggregation pipeline to achieve this:
$addFields: {
likes_reading: {
$cond: {
if: {
$eq: ["$likes.name", "reading"]
},
then: 1,
else: 0
}
}
}
However, $eq doesn't seem to be checking if "any" element of likes array has the required name, which is the kind of behaviour I've seen in many other places when we compare values with arrays. As a result all the likes_reading are set to 0. I need help setting this new field properly.

Since likes is an array you have to use $in operator so try this:
{
$addFields: {
likes_reading: {
$cond: {
if: { $in: ["reading", "$likes.name"] },
then: 1,
else: 0
}
}
}
}
Or
{
$addFields: {
likes_reading: {
$cond: [{ $in: ["reading", "$likes.name"] }, 1, 0]
}
}
}

Related

after aggregation how to check two fields are equal inside a document in mongodb

{
id: 1,
name: "sree",
userId: "001",
paymentData: {
user_Id: "001",
amount: 200
}
},
{
id: 1,
name: "sree",
userId: "001",
paymentData: {
user_Id: "002",
amount: 200
}
}
I got this result after unwind in aggregation any way to check user_Id equal to userId
Are you looking to only retrieve the results when they are equal (meaning you want to filter out documents where the values are not the same) or are you looking to add a field indicating whether the two are equal?
In either case, you append subsequent stage(s) to the aggregation pipeline to achieve your desired result. If you want to filter the documents, the new stage may be:
{
$match: {
$expr: {
$eq: [
"$userId",
"$paymentData.user_Id"
]
}
}
}
See how it works in this playground example.
If instead you want to add a field that compares the two values, then this stage may be what you are looking for:
{
$addFields: {
isEqual: {
$eq: [
"$userId",
"$paymentData.user_Id"
]
}
}
}
See how it works in this playground example.
You could also combine the two as in:
{
$addFields: {
isEqual: {
$eq: [
"$userId",
"$paymentData.user_Id"
]
}
}
},
{
$match: {
isEqual: true
}
}
Playground demonstration here

MongoDB - arrayFilters and updateMany from same document

I have the following sample of data:
{
_id: 1,
seniorityDate: '2001-01-01T00:00:00Z',
assigned: [
{
groupId: 11,
system: 'Dep',
effectiveDate: null
},
{
groupId: 12,
system: 'Team',
effectiveDate: null
},
...
]
}
and I would like to update the object effectiveDate based on seniorityDate in the array of assigned where system:'Team' only:
db.collection.updateMany({},
[{
$set: {
'assigned.$[elem].effectiveDate': '$seniorityDate'
}
}], {
arrayFilters: [{
"elem.system": "Team"
}]
})
but I got the following error:
arrayFilters may not be specified for pipeline-syle updates
The expected result will be:
{
_id: 1,
seniorityDate: '2001-01-01T00:00:00Z',
assigned: [
{
groupId: 11,
system: 'Dep',
effectiveDate: null
},
{
groupId: 12,
system: 'Team',
effectiveDate: '2001-01-01T00:00:00Z'
},
...
]
}
How can I achieve it?
You can't use the arrayFilters with the aggregation pipeline at the same time. While you are updating the value from another field, hence you can only achieve with aggregation pipeline.
$set - Set assigned field.
1.1. $map - Iterate element in assigned array and return new array.
1.1.1. $mergeObjects - Merge current iterated document with the document from 1.1.1.1.
1.1.1.1. Document with effectiveDate field. With the $cond operator, if matches the condition, use the seniorityDate value, else remain the existing value.
db.collection.updateMany({},
[
{
$set: {
"assigned": {
$map: {
input: "$assigned",
in: {
$mergeObjects: [
"$$this",
{
effectiveDate: {
$cond: {
if: {
$eq: [
"$$this.system",
"Team"
]
},
then: "$seniorityDate",
else: "$$this.effectiveDate"
}
}
}
]
}
}
}
}
}
])
Thanks to #rickhg12hs' suggestion, always limit the document for better performance, as you know which document/field should be updated by condition.
Hence your update query with query condition will be as below:
db.collection.updateMany({
"assigned.system": "Team"
},
[
...
])
Demo # Mongo Playground

How to conditionally set a value to parent field based on another field inside an array in mongodb aggregate?

I want to set a variable at a function level from inside the aggregate map operator. Here is a snippet of what I have till now.
$project: {
collectionId: 1,
name: 1,
description: 1,
image: 1,
created: 1,
updated: 1,
isPresent: "no",
"products": {
$map: {
"input": "$products",
as: "product",
in: {
"title": "$$product.name",
"imageUrl": "$$product.mainImage",
"productId": "$$product.productId",
"$isPresent": "yes"
}
}
},
total: {
$size: "$products"
},
userId: "$user.userId",
userName: "$user.name",
}
I want to set the isPresent node value to yes inside the map operator at in based on a condition. For the time being I have kept it to just yes. But this doesn't work.
What is the way to do it?
You can use $in and $cond operators :
db.collection.aggregate([
/** `$products.productId` gives an array of 'productId''s from products array */
{
$addFields: { isPresent: { $cond: [ { $in: [ 2, "$products.productId" ] }, "Yes", "No" ] } }
}
])
Test : mongoplayground
Note :
As we're using $in each document going through this has to have products array, otherwise $in will error out, if there are any such cases you need to have to condition to skip for those docs which don't have products array.

MongoDB, How Do I combine a find and sort with the $cond in aggregation?

I have written a find query, which works, the find query returns records where name and level exist
db.docs.find( { $and: [{name:{$exists:true}},{level:{ $exists:true}} ] },{_id:0, name:1}).sort({"name":1})
and now want to combine it with something like the code below which also works, but needs to be merged with the above to pull the correct data
db.docs.aggregate(
[
{
$project:
{
_id:0,
name: 1,
Honours:
{
$cond: { if: { $gte: [ "$level", 8 ] }, then: "True", else: "False" }
}
}
}
]
)
The find query returns records where name and level exist, but I need to enhance the result with new column called Honours, showing True of False depending on whether the level is gte (greater than or equal to 8)
So I am basically trying to combine the above find filter with the $cond function (which I found and modified example here : $cond)
I tried the below and a few other permutations to try and make find and sort with the $project and$cond aggregate, but it returned errors. I am just very new to how to construct mongodb syntax to make it all fit together. Can anyone please help?
db.docs.aggregate(
[{{ $and: [{name:{$exists:true}},{level:{ $exists:true}} ] },{_id:0, name:1}).sort({"name":1}
{
$project:
{
_id:0,
name: 1,
Honours:
{
$cond: { if: { $gte: [ "$level", 8 ] }, then: "True", else: "False" }
}
}
}
}
]
)
Try below aggregation pipeline :
db.docs.aggregate([
/** $match is used to filter docs kind of .find(), lessen the dataset size for further stages */
{
$match: {
$and: [{ name: { $exists: true } }, { level: { $exists: true } }]
}
},
/** $project works as projection - w.r.t. this projection it will lessen the each document size for further stages */
{
$project: {
_id: 0,
name: 1,
Honours: {
$cond: { if: { $gte: ["$level", 8] }, then: "True", else: "False" }
}
}
},
/** $sort should work as .sort() */
{ $sort: { name: 1 } }
]);

MongoDB sum with match

I have a collection with the following data structure:
{
_id: ObjectId,
text: 'This contains some text',
type: 'one',
category: {
name: 'Testing',
slug: 'test'
},
state: 'active'
}
What I'm ultimately trying to do is get a list of categories and counts. I'm using the following:
const query = [
{
$match: {
state: 'active'
}
},
{
$project: {
_id: 0,
categories: 1
}
},
{
$unwind: '$categories'
},
{
$group: {
_id: { category: '$categories.name', slug: '$categories.slug' },
count: { $sum: 1 }
}
}
]
This returns all categories (that are active) and the total counts for documents matching each category.
The problem is that I want to introduce two additional $match that should still return all the unique categories, but only affect the counts. For example, I'm trying to add a text search (which is indexed on the text field) and also a match for type.
I can't do this at the top of the pipeline because it would then only return categories that match, not only affect the $sum. So basically it would be like being able to add a $match within the $group only for the $sum. Haven't been able to find a solution for this and any help would be greatly appreciated. Thank you!
You can use $cond inside of your $group statement:
{
$group: {
_id: { category: '$categories.name', slug: '$categories.slug' },
count: { $sum: { $cond: [ { $eq: [ "$categories.type", "one" ] }, 1, 0 ] } }
}
}