mongodbexpressmongoosepushinsert-update

Update many documents by pushing an object to an array field when id field matches the id field in the object


Ok that question doesn't sound very clear. I will try to explain. I have a collection of F1 drivers that contains an array field to store the points earned from each race. I use a web form to submit the full result of a given race that contains the race_id, race_name, and a positions array that contains the driver_id and points earned as an object for each driver. I now need to update each driver in the collection with their result from this race by matching the driver id with the id in the object array. Ultimately; I will also need to update the total_points for each driver, but that can wait for now.

// The handler for my web form
function handleRaceResultSubmit(e) {
    e.preventDefault();

    let positions = [{id: null, points: 25}, {id: null, points: 18},{id: null, points: 15},{id: null, points: 12},{id: null, points: 10},
        {id: null, points: 8},{id: null, points: 6},{id: null, points: 4},{id: null, points: 2},{id: null, points: 1},
        {id: null, points: 0},{id: null, points: 0},{id: null, points: 0},{id: null, points: 0},{id: null, points: 0},
        {id: null, points: 0},{id: null, points: 0},{id: null, points: 0},{id: null, points: 0},{id: null, points: 0},];

    const eventID = parseInt(document.querySelector('#event-select').value);
    const eventName = document.querySelector('#event-select').textContent;
    const selectGroup = document.querySelectorAll('.select-group');

    selectGroup.forEach((driver, index) => {        
        positions[index].id = parseInt(driver.value);
    })

    const raceResult = {
        event_id: eventID,
        event_name: eventName,
        result: positions
    }

    updateDriverResults(raceResult);
}

// Post result to the endpoint
const updateDriverResults = async (data) => {
    fetch('driver-points',
    {
        method: "POST",
        headers: {"Content-Type": "application/json"},
        body: JSON.stringify(data)
    }).then(res => {return res.json()})
    .then(data => console.log(data))
    .catch(error => console.log(error));
}

//My endpoint and Mongoose query
app.post('/driver-points', express.json(), (req, res) => {

    Drivers.updateMany({
        'id': { $in: [ req.body.result.id ] },
    },
    {
        $push: {'results': {
            'event_id': req.body.event_id,
            'event_name': req.body.event_name,
            'points': req.body.result.points
        }}
    })    
    .then((result) => {
        res.send(result);
    })
    .catch((error) => {
        console.log(error);
    })
})

// The model
import { mongoose } from "mongoose";
const Schema = mongoose.Schema;

const driversSchema = new Schema({
    id: Number,
    driver_name: String,
    totla_points: Number,
    results: Array
});

const Drivers = mongoose.model('driver', driversSchema);

export { Drivers };

Solution

  • Just to give you a rough idea on how to solve this, I have prepared a sample in mongoplayground that shows how you can use an update with aggregation pipeline to perform complex updates for documents. Let's assume that there are 3 drivers in your collection with _id 1, 2 and 3. Drivers 1 and 2 already have some points from the first race whereas driver 3 does not have any results up to now.

    In the next race, drivers 1 and 3 reached points, so these two drivers should be updated so that

    1. The results field is initialized as an empty array if it is not present yet,
    2. The newly awarded points should be added to the results array,
    3. The driver _id is removed from the results field and
    4. The total field should be updated with the total number of points.

    The sample assumes that there is an array with the _ids of the drivers that should be updated ([1, 3]) and also an array that contains the results:

      [
        {
          _id: 1,
          race_id: 2,
          race_name: "Spa",
          points: 7
        },
        {
          _id: 3,
          race_id: 2,
          race_name: "Spa",
          points: 3
        }
      ]
    

    The following update statement is used to perform the updates:

    db.collection.update({
      _id: {
        $in: [
          1,
          3
        ]
      }
    },
    [
      {
        $set: {
          results: {
            $ifNull: [
              "$results",
              []
            ]
          }
        }
      },
      {
        $set: {
          results: {
            $concatArrays: [
              "$results",
              {
                $filter: {
                  input: [
                    {
                      _id: 1,
                      race_id: 2,
                      race_name: "Spa",
                      points: 7
                    },
                    {
                      _id: 3,
                      race_id: 2,
                      race_name: "Spa",
                      points: 3
                    }
                  ],
                  cond: {
                    $eq: [
                      "$$this._id",
                      "$_id"
                    ]
                  }
                }
              }
            ]
          }
        }
      },
      {
        $unset: "results._id"
      },
      {
        $set: {
          total: {
            $sum: "$results.points"
          }
        }
      }
    ],
    {
      multi: true
    })
    

    Please use this sample as a starting point that you can integrate into your mongoose environment.