mongodb update multiple Array elements using db.update() - mongodb

I compiled 2 update queries by referring to related stackoverflow answers, however, it doesn't seem to work, query updates all elements while only elements matching the criteria are expected to update.
Document:
[
{
"_id": 259,
"members": [
{
"email": "test1#gmail.com",
"added_by": "javapedia.net",
"status": "pending"
},
{
"email": "test2#gmail.com",
"added_by": "javapedia.net",
"status": "pending"
},
{
"email": "test3#gmail.com",
"status": "pending"
}
]
}
]
Query1: Using elemMatch operator, mongodb playground: https://mongoplayground.net/p/4cNgWJse86W
db.collection.update({
_id: 259,
"members": {
"$elemMatch": {
"email": {
"$in": [
"test3#gmail.com",
"test4#gmail.com"
]
}
}
}
},
{
"$set": {
"members.$[].status": "active"
}
},
{
"multi": true
})
Query2: using $in, mongodb playground : https://mongoplayground.net/p/tNu395B2RFx
db.collection.update({
_id: 259,
"members.email": {
"$in": [
"test3#gmail.com",
"test4#gmail.com"
]
}
},
{
"$set": {
"members.$[].status": "active"
}
},
{
"multi": true
})
Expected result: only one element with test3#gmail.com status should be updated to active.
Actual result: both queries update all records.

Is this what you're looking for?
db.collection.update({
_id: 259,
},
{
"$set": {
"members.$[el].status": "active"
}
},
{
arrayFilters: [
{
"el.email": {
$in: [
"test3#gmail.com",
"test4#gmail.com"
]
}
}
]
})
You can put the initial conditions back if needed, I just keep this short (and to me they make no sense).
multi:true isn't needed for one document
Maybe better semantically to use updateOne()

Related

MongoDB Aggregate Query to find the documents with missing values

I am having a huge collection of objects where the data is stored for different employees.
{
"employee": "Joe",
"areAllAttributesMatched": false,
"characteristics": [
{
"step": "A",
"name": "house",
"score": "1"
},
{
"step": "B",
"name": "car"
},
{
"step": "C",
"name": "job",
"score": "3"
}
]
}
There are cases where the score for an object is completely missing and I want to find out all these details from the database.
In order to do this, I have written the following query, but seems I am going wrong somewhere due to which it is not displaying the output.
I want the data in the following format for this query, so that it is easy to find out which employee is missing the score for which step and which name.
db.collection.aggregate([
{
"$unwind": "$characteristics"
},
{
"$match": {
"characteristics.score": {
"$exists": false
}
}
},
{
"$project": {
"employee": 1,
"name": "$characteristics.name",
"step": "$characteristics.step",
_id: 0
}
}
])
You need to use $exists to check the existence
playground
You can use $ifNull to handle both cases of 1. the score field is missing 2. score is null.
db.collection.aggregate([
{
"$unwind": "$characteristics"
},
{
"$match": {
$expr: {
$eq: [
{
"$ifNull": [
"$characteristics.score",
null
]
},
null
]
}
}
},
{
"$group": {
_id: null,
documents: {
$push: {
"employee": "$employee",
"name": "$characteristics.name",
"step": "$characteristics.step",
}
}
}
},
{
$project: {
_id: false
}
}
])
Here is the Mongo playground for your reference.

MongoDb sum issue after match and group

Suppose I have document as userDetails:
[
{
"roles": [
"author",
"reader"
],
"completed_roles": ["author", "reader"],
"address": {
"current_address": {
"city": "abc"
}
},
"is_verified": true
},
{
"roles": [
"reader"
],
"completed_roles": ["reader"],
"address": {
"current_address": {
"city": "abc"
}
},
"is_verified": true
},
{
"roles": [
"author"
],
"completed_roles": [],
"address": {
"current_address": {
"city": "xyz"
}
},
"is_verified": false
}
]
I want to fetch sum for all roles which has author based on city, total_roles_completed and is_verified.
So the O/P should look like:
[
{
"_id": {
"city": "abc"
},
"total_author": 1,
"total_roles_completed": 1,
"is_verified": 1
},
{
"_id": {
"city": "xyz"
},
"total_author": 1,
"total_roles_completed": 0,
"is_verified": 0
}
]
Basic O/P required:
Filter the document based on author in role (other roles may be present in role but author must be present)
Sum the author based on city
sum on basis of completed_profile has "author"
Sum on basis of documents if they are verified.
For this I tried as:
db.userDetails.aggregate([
{
$match: {
roles: {
$eleMatch: {
$eq: "author"
}
}
}
},
{
$unwind: "$completed_roles"
},
{
"$group": {
_id: { city: "$address.current_address.city"},
total_authors: {$sum: 1},
total_roles_completed: {
$sum: {
$cond: [
{
$eq: ["$completed_roles","author"]
}
]
}
},
is_verified: {
$sum: {
$cond: [
{
$eq: ["$is_verified",true]
}
]
}
}
}
}
]);
But the sum is incorrect. Please let me know where I made mistake. Also, if anyone needs any further information please let me know.
Edit: I figured that because of unwind it is giving me incorrect value, if I remove the unwind the sum is coming correct.
Is there any other way by which I can calculate the sum of total_roles_completed for each city?
If I've understood correctly you can try this query:
First $match to get only documents where roles contains author.
And then $group by the city (the document is not a valid JSON so I assume is address:{"current_addres:{city:"abc"}}). This $group get the authors for each city and also: $sum 1 if "author" is in completed_roles and check if is verified.
Here I don't know the way to know if the author is verified (I don't know if can be true in one document and false in other document. If is the same value over all documents you can use $first to get the first is_verified value). But I decided to use $allElementsTrue in a $project stage, so this only will be true if is_verified is true in all documents grouped by $group.
db.collection.aggregate([
{
"$match": {
"roles": "author"
}
},
{
"$group": {
"_id": "$address.current_address.city",
"total_author": {
"$sum": 1
},
"total_roles_completed": {
"$sum": {
"$cond": {
"if": {
"$in": [
"author",
"$completed_roles"
]
},
"then": 1,
"else": 0
}
}
},
"is_verified": {
"$addToSet": "$is_verified"
}
}
},
{
"$project": {
"_id": 0,
"city": "$_id",
"is_verified": {
"$allElementsTrue": "$is_verified"
},
"total_author": 1,
"total_roles_completed": 1
}
}
])
Example here
The result from this query is:
[
{
"city": "xyz",
"is_verified": false,
"total_author": 1,
"total_roles_completed": 0
},
{
"city": "abc",
"is_verified": true,
"total_author": 2,
"total_roles_completed": 2
}
]

Mongo $cond if expression doesn't work like $match

I have a collection with documents with a "parent" field.
[
{
"parent": "P1",
"tagGroups": [],
},
{
"parent": "P1",
"tagGroups": [
{
group: 1,
tags: {
tag1: {
value: true
},
tag2: {
value: "foo"
},
}
},
{
group: 2,
tags: {}
}
]
},
{
"parent": "P2",
"tagGroups": [],
}
]
I want to make request that retrieves all documents with the same parent when at least one match with my criteria: tag1.value = true.
Expected:
[
{
"parent": "P1",
"tagGroups": [],
},
{
"parent": "P1",
"tagGroups": [
{
group: 1,
tags: {
tag1: {
value: true
},
tag2: {
value: "foo"
},
}
},
{
group: 2,
tags: {}
}
]
}
]
For that I wanted to use the $cond to flag every document, then group by parent.
https://mongoplayground.net/p/WiIlVeLDrY-
But the "if" part seems to work differently that a $match
https://mongoplayground.net/p/_jcoUHE-aOu
Do you have another efficient way to do that kind of query?
Edit: I can use a lookup stage but I'm afraid of bad performances
Thanks
You haven't mentioned what you want to achieve, but you expect that your tried code (first link) should be working. You need to use $in instead of $eq in your query
db.collection.aggregate({
"$addFields": {
"match": {
"$cond": [
{ $in: [ true, "$tagGroups.tags.tag1.value" ] }, 1, 0] }
}
},
{
"$group": {
"_id": "$parent",
"elements": { "$addToSet": "$$ROOT" },
"elementsMatch": { "$sum": "$match" }
}
},
{ "$match": { "elementsMatch": { $gt: 0 } }},
{ "$unwind": "$elements"}
)
Working Mongo playground
Note : You have asked about the efficient way. Better you need to post expected result

count based on nested key mongodb

How can i count based on xTag key is on doc
I tried this but it does not provide me actual count
db.collection.find({
"products.xTag": {
$exists: false
}
}).count();
when you run with $exist:true i would expect result 1
When you run with $exist:false i would expect result 3
Playground: https://mongoplayground.net/p/_gf7RzGc8oB
Structure:
[
{
"item": 1,
"products": [
{
"name": "xyz",
"xTag": 32423
},
{
"name": "abc"
}
]
},
{
"item": 2,
"products": [
{
"name": "bob",
},
{
"name": "foo"
}
]
}
]
It is not possible with find(), You can use aggregate(),
$unwind deconstruct products array
$match your condition
$count total documents
db.collection.aggregate([
{ $unwind: "$products" },
{ $match: { "products.xTag": { $exists: false } } },
{ $count: "count" }
])
Playground

Fetch mongo documents based on multiple fields

Given mongo document of the following form in a collection:
{
"_id":"ObjectId",
"value":{
"id": 1,
"payment": [
{
"status": {
"id": "1.1",
"value": "Paid"
}
},
{
"status": {
"id": "1.2",
"value": "Scheduled"
}
},
{
"status": {
"id": "1.3",
"value": "Recorded"
}
}
]
}
}
ids = [1,2,3,4]
How can i fetch all documents having id in ids and at least one of payments.status.value equal to Scheduled state ?
I am using the following query but it's returning 0 records,
db.collectionName.find({$and:[{"value.id":{$in:ids}},{"value.payment.status.value":"Scheduled"}]})`
you can specify the name of the collection
so instead of this:
db.collection.find({
$and:
[
{"value.id": {$in:ids}},
{"value.payment.status.value":"Scheduled"}
]
})
you can write:
db.payments.find({
$and:
[
{"value.id":{$in:ids}},
{"value.payment.status.value":"Scheduled"}
]
})
db.collection.find({
'value.payment': {
$elemMatch: {
'status.value': 'Scheduled'
}
},
'value.id': {
$in: [1, 2, 3, 4]
}
}, {
'value.id': 1,
'value.payment.$.status': 1
})