$size operator is inserted as static string in mongodb upsert - mongodb

In MongoDB, I want to have a document similar to this:
{
"project": "myproject",
"tasks": ["mytask"],
"tasksCount": 1
}
where tasksCount is the length of the tasks array. I use this upsert query as suggested here:
db.tasks.updateOne(
{ project: "myproject" },
{
$addToSet: { tasks: "mytask" },
$set: { tasksCount: { $size: "$tasks" } }
},
{ upsert: true }
)
But the actual document inserted, looks like this:
{
"project": "myproject",
"tasks": ["mytask"],
"tasksCount": {
"$size": "$tasks"
}
}
where the tasksCount is simply the object I pass in, instead of the actual size.
I am using mongo v5.0.8.

As #turvishal mentioned in the comment $size is an aggregation operator , here is an option using update with aggregation framework:
db.collection.update({
project: "myproject"
},
[
{
$project: {
tasks: {
$setUnion: [
"$tasks",
[
"mytask2"
]
]
}
}
},
{
$addFields: {
tasksCount: {
$size: "$tasks"
}
}
}
],
{
upsert: true,
multi: true
})
Explained:
Match all documents with project:"myproject"
Project $setUnion ( addToSet) to add the task to the tasks array.
Replace the field tasksCount with the size of the tasks array.
Upsert:true + multi:true to perform the operation for all matching and insert if there is no matchiing.
Plaground

Related

get document with same 3 fields in a collection

i have a collection with more then 1000 documents and there are some documents with same value in some fields, i need to get those
the collection is:
[{_id,fields1,fields2,fields3,etc...}]
what query can i use to get all the elements that have the same 3 fields for example:
[
{_id:1,fields1:'a',fields2:1,fields3:'z'},
{_id:2,fields1:'a',fields2:1,fields3:'z'},
{_id:3,fields1:'f',fields2:2,fields3:'g'},
{_id:4,fields1:'f',fields2:2,fields3:'g'},
{_id:5,fields1:'j',fields2:3,fields3:'g'},
]
i need to get
[
{_id:2,fields1:'a',fields2:1,fields3:'z'},
{_id:4,fields1:'f',fields2:2,fields3:'g'},
]
in this way i can easly get a list of "duplicate" that i can delete if needed, it's not really important get id 2 and 4 or 1 and 3
but 5 would never be included as it's not 'duplicated'
EDIT:
sorry but i forgot to mention that there are some document with null value i need to exclude those
This is the perfect use case of window field. You can use $setWindowFields to compute $rank in the grouping/partition you want. Then, get those rank not equal to 1 to get the duplicates.
db.collection.aggregate([
{
$match: {
fields1: {
$ne: null
},
fields2: {
$ne: null
},
fields3: {
$ne: null
}
}
},
{
"$setWindowFields": {
"partitionBy": {
fields1: "$fields1",
fields2: "$fields2",
fields3: "$fields3"
},
"sortBy": {
"_id": 1
},
"output": {
"duplicateRank": {
"$rank": {}
}
}
}
},
{
$match: {
duplicateRank: {
$ne: 1
}
}
},
{
$unset: "duplicateRank"
}
])
Mongo Playground
I think you can try this aggregation query:
First group by the feilds you want to know if there are multiple values.
It creates an array with the _ids that are repeated.
Then get only where there is more than one ($match).
And last project to get the desired output. I've used the first _id found.
db.collection.aggregate([
{
"$group": {
"_id": {
"fields1": "$fields1",
"fields2": "$fields2",
"fields3": "$fields3"
},
"duplicatesIds": {
"$push": "$_id"
}
}
},
{
"$match": {
"$expr": {
"$gt": [
{
"$size": "$duplicatesIds"
},
1
]
}
}
},
{
"$project": {
"_id": {
"$arrayElemAt": [
"$duplicatesIds",
0
]
},
"fields1": "$_id.fields1",
"fields2": "$_id.fields3",
"fields3": "$_id.fields2"
}
}
])
Example here

MongoDB - Update the value of one field with the value of another nested field

I am trying to run a MongoDB query to update the value of one field with the value of another nested field. I have the following document:
{
"name": "name",
"address": "address",
"times": 10,
"snapshots": [
{
"dayTotal": 2,
"dayHit": 2,
"dayIndex": 2
},
{
"dayTotal": 3,
"dayHit": 3,
"dayIndex": 3
}
]
}
I am trying like this:
db.netGraphMetadataDTO.updateMany(
{ },
[{ $set: { times: "$snapshots.$[elem].dayTotal" } }],
{
arrayFilters: [{"elem.dayIndex":{"$eq": 2}}],
upsert: false,
multi: true
}
);
but got an error:
arrayFilters may not be specified for pipeline-syle updates
You can't use arrayFilters with aggregation pipeline for update query at the same time.
Instead, what you need to do:
Get the dayTotal field from the result 2.
Take the first matched document from the result 3.
Filter the document from snapshots array.
db.netGraphMetadataDTO.updateMany({},
[
{
$set: {
times: {
$getField: {
field: "dayTotal",
input: {
$first: {
$filter: {
input: "$snapshots",
cond: {
$eq: [
"$$this.dayIndex",
2
]
}
}
}
}
}
}
}
}
],
{
upsert: false,
multi: true
})
Demo # Mongo Playground

MongoDB - How to rename the specific field from list of unstructured array field?

I have several documents as given below. Now I need to do rename the middlename field into mid_name if middlename exists in the document.
{
"id":"abc",
"name":[
{
"first_name":"abc",
"last_name":"def"
},
{
"first_name":"ghi",
"last_name":"mno",
"middilename":"xyz"
}
]
}
This is something that I expect it to be.
{
"id":"abc",
"name":[
{
"first_name":"abc",
"last_name":"def"
},
{
"first_name":"ghi",
"last_name":"mno",
"mid_name":"xyz"
}
]
}
And this is what I have done but it throws the error.
db.md_carrierInformation.updateMany({"name.middlename":{$exists:true}}, {$rename:{"name.$.middlename":"name.mid_name"}})
ERROR
MongoServerError: The source field for $rename may not be dynamic: name.$.middlename
Work on the update with the aggregation pipeline.
$set - Set name array field.
1.1. $map - Iterate each item in name array and return new array.
1.2. $cond - Condition for checking current document's middlename is not existed.
1.2.1. If true, with merge current document with the document with field mid_name via $mergeObjects.
1.2.2. If false, remain the existing document.
$unset - Remove field for name.middlename.
db.md_carrierInformation.updateMany({
"name.middlename": {
$exists: true
}
},
[
{
$set: {
"name": {
$map: {
input: "$name",
in: {
$cond: {
if: {
$ne: [
"$$this.middlename",
undefined
]
},
then: {
$mergeObjects: [
"$$this",
{
mid_name: "$$this.middlename"
}
]
},
else: "$$this"
}
}
}
}
}
},
{
$unset: "name.middlename"
}
])
Sample Mongo Playground

MongoDB conditionial update

I am running a bit in circles here and would appreciate some help. What I am looking to do is either update or create a nested object contained in an array depending on whether this object exists.
I have a users collection and a user document has the following structure:
{
schema_version: 1,
display_name: 'xxxxxx',
email: 'xxxxxx',
email_verified:'xxxxxx',
...
custom_data: {
stripe_id: 'xxx',
subscriptions: [{
subscription_id: xxxx,
....
}],
...
},
}
In webhook calls from Stripe I am getting a subscription object with a subscription_id and a stripe_id.
What I want to do is check if subscription_id exists, if so, update the document, if not then create the document in the subscriptions array for the user document where stripe_id matches.
If I do something along the lines of:
db.collection.update(
{subscription_id: subscription.id},
{ $set: { 'custom_data.subscriptions': subscriptionData } },
{ upsert: true }
)
The problem is that I am creating subscription objects not bound to my user document where stripeID matches.
On the other hand, if I do something like this:
db.collection.update(
{'custom_data.stripe_id': stripe_id},
{ $set: { 'custom_data.subscriptions': subscriptionData } },
{ upsert: true }
)
I will potentially end up creating dupes in the subscriptions array when, in fact I would want to update the existing object where subscription_id matches.
Is there any way to do that in one query with Mongo, or will I have to resort to using 2 queries in an if statement?
Thanks in advance for any clarification on this.
You can do the followings with an aggregation pipeline:
$match with $or condition to search for custom_data.subscriptions.subscription_id or custom_data.stripe_id
$addFields with $map to conditional update your subscription object when matched
$addFields with $setUnion to insert an entry of incoming subscription object for the insert case
$merge to update the back into the original collection
db.collection.aggregate([
{
$match: {
$expr: {
$or: [
{
$eq: [
"$custom_data.subscriptions.subscription_id",
"xxxx"
]
},
{
$eq: [
"$custom_data.stripe_id",
"xxx"
]
}
]
}
}
},
{
"$addFields": {
"custom_data": {
subscriptions: {
"$map": {
"input": "$custom_data.subscriptions",
"as": "s",
"in": {
"$cond": {
// if subscription_id matched, replace with your incoming object
"if": {
$eq: [
"$$s.subscription_id",
"xxxx"
]
},
"then": {
subscription_id: "incoming_sub_id"
},
// if not matched, keep the original object
"else": "$$s"
}
}
}
}
}
}
},
{
"$addFields": {
"custom_data": {
subscriptions: {
// insert case; if the subscription array is empty, then union with your incoming object
$setUnion: [
"$custom_data.subscriptions",
[
{
subscription_id: "incoming_sub_id"
}
]
]
}
}
}
},
{
"$merge": {
"into": "collection",
"on": "_id",
"whenMatched": "replace"
}
}
])
Here is the Mongo playground for your reference.

Mongo DB aggregate match not returning value

I have the following mongo db schema and I am trying to build an aggregate query that searches under github_open_issues under the repo key and can return me a match for all the values with repoA as the value. I have tried the following as my query however its not returning any result. Im a bit confused why this is not working as I have another db with a schema similar to this and this type of query works there but here something seems to be different and is not working. I have also put together this interactive example mongoplayground
query
db.collection.aggregate([
{
"$unwind": "$github_open_issues"
},
{
"$match": {
"github_open_issues.repo": {
"$in": [
"repoA"
]
}
}
},
])
schema
[
{
"github_open_issues": {
"0": {
"git_url": "https://github.com/",
"git_assignees": "None",
"git_open_date": "2019-09-26",
"git_id": 253113,
"repo": "repoA",
"git_user": "userA",
"state": "open"
},
"1": {
"git_url": "https://github.com/",
"git_assignees": "None",
"git_open_date": "2019-11-15",
"git_id": 294398,
"repo": "repoB",
"git_user": "userB",
"state": "open"
},
"2": {
"git_url": "https://github.com/",
"git_assignees": "None",
"git_open_date": "2021-04-12",
"git_id": 661208,
"repo": "repoA",
"state": "open"
}
},
"unique_label_seen": {
"568": {
"label_name": "some label",
"times_seen": 12,
"535": {
"label_name": "another label",
"times_seen": 1
}
}
}
}
]
$objectToArray convert github_open_issues object to array in key-value format
$filter to iterate loop of above converted array and filter your search condition
$match to filter github_open_issues not empty
$arrayToObject convert github_open_issues array to object
db.collection.aggregate([
{
$addFields: {
github_open_issues: {
$filter: {
input: { $objectToArray: "$github_open_issues" },
cond: { $in: ["$$this.v.repo", ["repoA"]] }
}
}
}
},
{ $match: { github_open_issues: { $ne: [] } } },
{ $addFields: { github_open_issues: { $arrayToObject: "$github_open_issues" } } }
])
Playground
You query is correct but you data in schema placed wrong inside github_open_issues.repo your objects are place by numbers like {"0": {values... }, "1":{values... }} which cannot get your desired value. You can check the playground now playground