Updating an element in an array at a specific index in MongoDB and adding a new document if no match is found

UPDATE: My main goal with this query is to find a quick solution, as I am new to MongoDB and assumed using a single query would be the fastest option. However, any fast solution will suffice.

Essentially, I am seeking a solution for the following issue. With hundreds of similar operations occurring simultaneously, speed is a crucial factor.

I have a collection called curves with documents structured like this:

{
    curveId: 12,
    values: [1, 2, 12, 7, ...]
}

Let's say I want to update a curve value at index 3, resulting in:

{
    curveId: 12,
    values: [1, 2, 12, new_value, ...]
}

If there is no matching curveId, a new curve should be created like this:

{
    curveId: 12,
    values: [null, null, null, new_value]
}

To achieve this, I have written an upsert query:

db.curves.update({
    curveId: 12
},{
    $set: { "values.3": new_value }
},{
    upsert: true
})

This query successfully updates an existing document but creates a new document like this if there is no match:

{
    curveId: 12,
    values: { '3': new_value }
}

The values field is not an array as expected.

After researching extensively, I have yet to find a solution. Is it feasible to solve this problem with a single query?

Thank you.

Answer №1

This method employs the use of $concatArrays and $slice when the value array is present and meets the required minimum size. In cases where these conditions are not met, it resorts to using $zip and $map to fill in any missing values. This approach offers the advantage of avoiding the use of $map when the specified criteria are satisfied.

  • It's worth noting that $map is not necessarily a "slow" operation, especially in this context or the one that was discussed in the comments.
  • Comparatively, document retrieval for the update process is significantly slower.
  • I have shared a separate answer which consistently utilizes $map, similar to the approach mentioned in cmghess's comment, but incorporates concatArrays and slice.
  • If achieving nano-level performance gains by avoiding the use of $map is crucial, then implementing multiple conditions with $switch-case would be recommended, as it allows for the optimization of each specific case outlined below (existence, size validation, null value, etc.).

This Update Pipeline addresses the following scenarios:

[
  { "_id": "curveId exists and is larger", "curveId": 12, "values": [1, 2, 3, 7, 8, 9] },
  { "_id": "curveId exists and values is exact", "curveId": 12, "values": [1, 2, 3, 4] },
  { "_id": "curveId exists but values is short by 1", "curveId": 12, "values": [1, 2, 3] },
  { "_id": "curveId exists but values is short by 2", "curveId": 12, "values": [1, 2] },
  { "_id": "curveId exists but values is empty", "curveId": 12, "values": [] },
  { "_id": "curveId exists but values is null", "curveId": 12, "values": null },
  { "_id": "curveId exists but values does not exist", "curveId": 12 },
  { "_id": "curveId 99 doesn't exist" }
]

Update pipeline with aggregation expressions:

db.collection.update({ curveId: 12 },
[
  {
    $set: {
      values: {
        $let: {
          vars: {
            // index & new value to set
            idx: 3,
            new_val: 1000
          },
          in: {
            $cond: {
              if: {
                $and: [
                  { $isArray: "$values" },
                  { $lte: [ "$$idx", { $size: "$values" }] }  // "lte" is correct here
                ]
              },
              then: {
                $concatArrays: [
                  { $slice: ["$values", "$$idx"] },
                  ["$$new_val"],
                  {
                    $slice: [
                      "$values",
                      { $add: ["$$idx", 1] },
                      { $add: [{ $size: "$values" }, 1] }
                    ]
                  }
                ]
              },
              else: {
                $let: {
                  vars: {
                    vals_nulls: {
                      $map: {
                        input: {
                          $zip: {
                            inputs: [
                              { $ifNull: ["$values", []] },
                              { $range: [0, "$$idx"] }
                            ],
                            useLongestLength: true
                          }
                        },
                        in: { $first: "$$this" }
                      }
                    }
                  },
                  in: {
                    $concatArrays: [
                      { $slice: ["$$vals_nulls", "$$idx"] },
                      ["$$new_val"],
                      {
                        $slice: [
                          "$$vals_nulls",
                          { $add: ["$$idx", 1 ] },
                          { $add: [{ $size: "$$vals_nulls" }, 1] }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        }
      }
    }
  }
],
{ upsert: true, multi: true }
)

Note:

  • The new_value and index for setting purposes only require placement in the initial $set -> values -> $let segment
    • All subsequent references to these values are addressed as variables.
    • If available, utilize your language's let parameter rather than this approach; mongoose and playground do not offer this feature.
  • multi: true is solely utilized to demonstrate all update scenarios in a single execution.
  • To observe the upsert behavior when the document is absent, use curveId: 99 (any value other than 12) in the query.
  • Take note of the repetition that occurs within the else segment involving $values and $$vals_nulls.

Mongo Playground

Mongo Playground with index=0

Answer №2

This new approach utilizes the $map function consistently, along with $concatArrays and $slice. It offers the benefit of being concise and more organized compared to my previous method. Additionally, using $map does not significantly impact performance if document retrieval has already taken place.

Similar to the previous method, it generates an array of "values or nulls" that either populates, creates, or pads null values to meet the required size based on the specified index. Through the use of $zip and $first: "$$this", it generates an array containing values from values or null as placeholders when they are missing or need to be padded.

db.collection.update({ curveId: 12 },
[
  {
    $set: {
      values: {
        $let: {
          vars: {
            // index & new value to set
            idx: 3,
            new_val: 1000,
            vals_nulls: {
              $map: {
                input: {
                  $zip: {
                    inputs: [
                      { $ifNull: ["$values", []] },
                      { $range: [0, 3] }  // repeat index here :-(
                    ],
                    useLongestLength: true
                  }
                },
                in: { $first: "$$this" }
              }
            }
          },
          in: {
            $concatArrays: [
              { $slice: ["$$vals_nulls", "$$idx"] },
              ["$$new_val"],
              {
                $slice: [
                  "$$vals_nulls",
                  { $add: ["$$idx", 1] },
                  { $add: [{ $size: "$$vals_nulls" }, 1] }
                ]
              }
            ]
          }
        }
      }
    }
  }
],
{ upsert: true, multi: true }
)

Mongo Playground

Mongo Playground with index=0

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the method for showcasing background images sequentially?

css code #intro { position: relative; background-attachment: fixed; background-repeat: no-repeat; background-position: center top; -webkit-background-size: cover; -moz-background-size: cover; backgr ...

Demonstrate a array of values at varying angles within a circle using the functionalities of HTML5 canvas

Looking to utilize HTML5 Canvas and Javascript for a project where I need to showcase various values (depicted by dots possibly) at different angles within a circle. For example, data could include: val 34% @ 0°, val 54% @ 12°, val 23% @ 70°, a ...

I wish for the value of one input field to always mirror the value of another input field

There is a checkbox available for selecting the billing address to be the same as the mailing address. If the checkbox is checked, both values will remain the same, even if one of them is changed. Currently, I have successfully achieved copying the mailing ...

angularjs dynamically display expression based on controller value

I'm not the best at explaining things, so I hope you all can understand my needs and provide some assistance here. Below is a view using ng-repeat: <div ng-repeat="item in allitems"> {{displaydata}} </div> In my controller, I have the f ...

Exporting a named export for every HTTP method is the way to go with NextJs

I'm currently working on a project to create an Airbnb clone using NextJs. The tutorial I'm following is using an experimental version, while I've opted for the latest version of NextJs. One aspect that's been causing confusion for me i ...

Exploring AngularJS: Utilizing limitTo and filter

I'm having some trouble with using angular's limitTo and filter together. I want to search for a value in the search box, then apply a limit to the number of results displayed by entering a number in the filter box and clicking apply filter. Howe ...

Tips for creating a custom axios response depending on the error code returned by the response

I am currently working on implementing a global error handling system in my Vue application. Within my project, I have an api.service.js file that contains the necessary code for Axios setup and various HTTP request functions such as get and post: /** * S ...

Is there a way to ensure the content of two divs remains aligned despite changing data within them?

Currently, I have two separate Divs - one displaying temperature data and the other showing humidity levels. <div class="weatherwrap"> <div class="tempwrap" title="Current Temperature"> ...

Tips for transferring data via ajax to rails 3. The jQuery function is ensuring the string is properly handled

I am attempting to achieve the following: $.ajax({ type: "PUT", url: /example, data: {_method: "put", "foo_model[bar_attribute]": value_var } }); It is working as expected, but I need to dynamically construct the part foo_model[bar_attribute]. ...

How is the height or width of the Bootstrap modal determined?

I'm having trouble utilizing Jschr's modified Bootstrap-Modal for different modal sizes. I can't seem to understand why some modals appear taller, wider, or have other specific dimensions. For more information, refer to the documentation: ...

Brick-themed HTML/CSS elements drift away from each other

I'm currently designing an image collage for my website and attempted to use masonry for the layout. However, when I adjust the size of the blocks, they seem to drift apart, creating large gaps between each block. Any suggestions on how to resolve thi ...

The application suddenly displays a blank white screen after encapsulating my layout with a context provider

Here is the custom layout I created: export const metadata = { title: "Web App", description: "First Project in Next.js", }; export default function CustomLayout({ children }) { return ( <html lang="en"> ...

Although my service worker's IndexedDB add() operation was successful, I am unable to view the data in the Chrome dev tools Application/Storage section

Currently running Chrome Version 57.0.2987.110 (64-bit) on MacOS/OSX 12.3 Sierra, I have recently set up a service worker, but I am relatively new to this. Now I am working on integrating an IndexedDB to store data. The service worker successfully fetch ...

What are the best ways to make this date more visually appealing and easy to understand?

Hey there! So I have a date that looks like this: 2022-06-28T17:09:00.922108+01:00 I'm trying to make it more readable, but unfortunately, when I attempted using moment-js in my javascript/react project, it threw an error stating "invalid format". ...

Having trouble loading CSS file in node js after submitting form

Before diving into more details, let me explain my current situation. I am utilizing node's nodemailer to facilitate the process of sending emails based on the information submitted through a form. Initially, everything was functioning smoothly when t ...

Can you tell me how to display the currently utilized Node.js version and path within npm?

I’m encountering a similar issue to the one discussed in this Stackoverflow thread: SyntaxError: Use of const in strict mode?. However, my problem arises when attempting to install Flux. I followed the steps outlined in the linked Stackoverflow question ...

Colorful D3.js heatmap display

Hello, I am currently working on incorporating a color scale into my heat map using the d3.schemeRdYlBu color scheme. However, I am facing challenges in getting it to work properly as it only displays black at the moment. While I have also implemented a ...

JavaScript closures and the misinterpretation of returning values

function generateUniqueCelebrityIDs(celebrities) { var i; var uniqueID = 100; for (i = 0; i < celebrities.length; i++) { celebrities[i]["id"] = function () { return uniqueID + i; }; }; return celebrities; ...

Testing with Phantom/Casper in a browser-based environment

Currently, I am utilizing Casper for UI testing on websites. My main concern is regarding the compatibility testing in various browsers such as IE, Chrome, and Firefox using Casper. If this cannot be achieved with Casper, I am open to alternative methods ...

Issue with resizing Ionic carousel when making an $http request

In my Ionic project, I am utilizing a plugin to create a carousel (https://github.com/ksachdeva/angular-swiper). The demo of this plugin includes a simple repeat function. However, when I replaced the default repeat with my own using $http, it caused an is ...