Adrian Sultu
Adrian Sultu

Reputation: 350

mongoose check if id exists but that id is nested inside an array

When i fetch new alerts, i want to check if the ID of the new alert was already recorded. The issue is that that ID is nested inside an array. There's the alertsDetails array, which contains objects and those objects have an _ID filed which is what i want to check. I am not sure how to achieve that. I got the code below but then i have to iterate over the result to check the exists value. Im sure there must be a better way.

const mongoose = require('mongoose');

const { Schema } = mongoose;

const G2AlertsSchema = new Schema(
  {
    status: { type: String, required: true },
    openDate: { type: Date, required: true },
    alertType: { type: Array, required: true },
    severity: { type: Array, required: true },
    locationName: { type: Array, required: true },
    history: { type: Array, required: true },
    alertDetails: { type: Array, required: false },
    assignedTo: { type: Schema.Types.ObjectId, ref: 'user' },
  },
  {
    timestamps: true,
  },
);

const G2Alerts = mongoose.model('G2Alert', G2AlertsSchema);

module.exports = G2Alerts;

This is the code i found on mongodb's website. I just want to see if the ID exists only. Basically when i fetch the new alerts i get an array and i iterate over it, i want to check each item's ID against what's inside the Database. If it's there, skip and go to the next. If it's new, then create a new alert and save it.

 const exists = await G2Alerts.aggregate([
    {
      $project: {
        exists: {
          $in: ['5f0b4f508bda3805754ab343', '$alertDetails._id'],
        },
      },
    },
  ]);

EDIT: Another thing. I am getting a eslint warning saying i should use array iteration instead of a for loop. The issue is, i need to use await when looking up the Alert ID. If i use, reduce or filter, i can't use await. If i use async inside the reduce or filter function, then it will return promises in or just an empty array.

This below works, based on the answer provided by Tom Slabbaert

  const newAlertsData = [];

  for (let item of alertData.data.items) {
    const exists = await G2Alerts.find({ 'alertDetails._id': `${item._id}` });
    if (exists.length === 0) {
      newAlertsData.push(item);
    }
  }

  if (newAlertsData.length !== 0) {......

But this does not

  const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
    const exists = await G2Alerts.find({ 'alertDetails._id': `${item._id}` });
    if (exists.length === 0) {
      filtered.push(item);
    }
    return filtered;
  }, []);

enter image description here

Upvotes: 2

Views: 10104

Answers (1)

Tom Slabbaert
Tom Slabbaert

Reputation: 22276

You're not far off, here is an example using the correct syntax:

const exists = await G2Alerts.findOne({"alertDetails._id": '5f0b4f508bda3805754ab343'}});
if (!exists) {
    ... do something
}

This can also be achieve using aggregate with a $match stage instead of a $project stage or even better countDocuments which just returns the count instead of the entire object if you do not require it.

One more thing I'd like to add is that make sure alertDetails._id is string type as you're using string in you're $in. otherwise you'll need to cast them to ObjectId type in mongoose like so:

new mongoose.Types.ObjectId('5f0b4f508bda3805754ab343')

And for Mongo:

import {ObjectId} from "mongodb"
...


new ObjectId('5f0b4f508bda3805754ab343')

EDIT

Try something like this?

let ids = alertData.data.items.map(item => item._id.toString());
let existing = await G2Alerts.distinct("alertsDetails._id", {"alertsDetails._id": {$in: ids}});

const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
    if (!existing.includes(item._id.toString())) {
        return [item].concat(filtered)
    }
    return filtered;
}, []);

This way you only need to call the db once and not multiple times.

Final code based on the provided answer.

  const ids = alertData.data.items.map(item => item._id);
  const existing = await G2Alerts.find({ 'alertDetails._id': { $in: ids } }).distinct(
    'alertDetails._id',
    (err, alerts) => {
      if (err) {
        res.send(err);
      }
      return alerts;
    },
  );

  const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
    if (!existing.includes(item._id.toString()) && item.openDate > dateLimit) {
      return [item].concat(filtered);
    }
    return filtered;
  }, []);

Upvotes: 3

Related Questions