Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally updating items in mongoose query

I have the following code and I'm trying to do two things. First I want to have my query have one condition where it finds the 'originator' value in a doc, but the second par of that is not to update if is also finds 'owner_id' is the same as originator.

The second part of what I'm trying to do is only set/update a field is it is being passed in. Can I use a ternary statement, something like below???

  Contacts.update(
    {
      'originator': profile.owner_id,
      'owner_id': !profile.owner_id
    }, 
    {
      $set: {
        (phoneNumber) ? ('shared.phones.$.phone_number': phoneNumber):null,
        (emailAddress) ? ('shared.emails.$.email_address': emailAddress):null
      }
    }, 
    {
      'multi': true
    },
    function(err) {
      err === null ? console.log('No errors phone updated for contacts.shared') : console.log('Error: ', err);
    }
  ) 
like image 602
cnak2 Avatar asked Dec 07 '22 13:12

cnak2


1 Answers

You mean something like this:

var updateBlock = {};
if (phoneNumber)
  updateBlock['shared.phones.$.phone_number'] = phoneNumber;
if (emailAddress)
  updateBlock['shared.email.$.email_address'] = emailAddress;

Contacts.updateMany(
  { 
    "originator": profile.owner_id
    "owner_id": { "$ne": profile.owner_id }
  },
  { "$set": updateBlock },
  function(err, numAffected) {
     // work with callback
  }
)

That addresses your two "main" misconceptions here in that the "inequality" in the query condition requires the $ne operator and not the ! JavaScript expression. MongoDB does not use JavaScript expressions here for the query conditions.

The second "main" misconception is the construction of the "update block" with conditional keys. This is by contrast a "JavaScript Object" which you construct separately in order to specify only the keys you wish to effect.

However there is STILL A PROBLEM in that you want to use the positional $ operator. Presuming you actually have "arrays" in the document like this:

{
   "originator": "Bill",
   "owner_id": "Ted",
   "shared": {
     "phones": [ "5555 5555", "4444 4444" ],
     "email": [ "[email protected]", "[email protected]" ]
   }
}

Then your "two-fold" new issue is that:

  1. You must specify a query condition that matches the array element "in the query block" in order to obtain the "matched position" at which to update.

  2. You can only return ONE matched array index via use of the positional $ operator and NOT TWO as would be inherent to updating such a document.

For those reasons ( and others ) it is strongly discouraged to have "multiple arrays" within a single document. The far better approach is to use a "singular" array, and use properties to denote what "type" of entry the list item actually contains:

{
   "originator": "Bill",
   "owner_id": "Ted",
   "shared": [
     { "type": "phone", "value": "5555 5555" },
     { "type": "phone", "value": "4444 4444" },
     { "type": "email", "value": "[email protected]" },
     { "type": "email", "value": "[email protected]" }
   ]
}

In this way you can actually address the "matched" element in which to update:

// phoneNumberMatch = "4444 4444";
// phoneNumber = "7777 7777";
// emailAddress = null;            // don't want this one
// emailAddressMatch = null;       // or this one
// profile = { owner_id: "Bill" };

var query = {
  "originator": profile.owner_id,
  "owner_id": { "$ne": profile.owner_id },
  "shared": {
    "$elemMatch": {
      "type": (phoneNumber) ? "phone" : "email",
      "value": (phoneNumber) ? phoneNumberMatch : emailAddressMatch
    }
  }
};

var updateBlock = {
  "$set": {
    "shared.$.value": (phoneNumber) ? phoneNumber : emailAddress
  }
};

Contacts.updateMany(query, updateBlock, function(err, numAffected) {
  // work with callback
})

In such a case and with a "binary" choice then you "can" use ternary conditions in construction since you are not reliant on "naming keys" within the construction.

If you want "either, or indeed both" supplied values in combination then you need a bit more advanced statement:

// phoneNumberMatch = "5555 5555";
// phoneNumber = "7777 7777";
// emailAddress = "[email protected]";
// emailAddressMatch = "[email protected]";
// profile = { owner_id: "Bill" };

var query = {
  "originator": profile.owner_id,
  "owner_id": { "$ne": profile.owner_id },
  "$or": []
};

var updateBlock = { "$set": {} };
var arrayFilters = [];

if (phoneNumber) {
  // Add $or condition for document match
  query.$or.push(
    {
      "shared.type": "phone",
      "shared.value": phoneNumberMatch
    }
  );

  // Add update statement with named identifier
  updateBlock.$set['shared.$[phone].value'] = phoneNumber;

  // Add filter condition for named identifier
  arrayFilters.push({
    "phone.type": "phone",
    "phone.value": phoneNumberMatch
  })
}

if (emailAddress) {
  // Add $or condition for document match
  query.$or.push(
    {
      "shared.type": "email",
      "shared.value": emailAddressMatch
    }
  );

  // Add update statement with named identifier
  updateBlock.$set['shared.$[email].value'] = emailAddress;

  // Add filter condition for named identifier
  arrayFilters.push({
    "email.type": "email",
    "email.value": emailAddressMatch
  })
}

Contacts.updateMany(query, updateBlock, arrayFilters, function(err, numAffected) {
  // work with callback
})

Noting of course here that the positional filtered $[<identifier>] syntax from MongoDB 3.6 and upwards is required in order to effect multiple array elements within a single update statement.

Much the same applies to the "original" structure I first described using "multiple" arrays in the documents instead of named properties on a "singular" array as the above examples deal with:

var query = {
  "originator": "Bill",
  "owner_id": { "$ne": "Bill" },
  "$or": []
};

var updateBlock = { "$set": {} };
var arrayFilters = [];

if (phoneNumber) {
  query.$or.push({
    "shared.phones": phoneNumberMatch
  });

  updateBlock.$set['shared.phones.$[phone]'] = phoneNumber;

  arrayFilters.push({
    "phone": phoneNumberMatch
  });
}

if (emailAddress) {
  query.$or.push({
    "shared.email": emailAddressMatch
  });

  updateBlock.$set['shared.email.$[email]'] = emailAddress;

  arrayFilters.push({
    "email": emailAddressMatch
  });
}

Contacts.updateMany(query, updateBlock, arrayFilters, function(err, numAffected) {
  // work with callback
})

Of course if you don't even have arrays at all ( the question posted lacks any example document ) then positional matches are not even needed in any form, but you do however still "conditionally" construct JavaScript object "keys" via construction code blocks. You cannot "conditionally" specify a "key" in JSON-like notation.

like image 137
Neil Lunn Avatar answered Dec 11 '22 07:12

Neil Lunn