insert always creates. update always changes. upsert decides for itself: given a
key, it creates the record when the key is new and changes it when the key already
exists. That is the operation an integration needs, because the incoming data does not
say which of the two it is.
An upsert needs a field that identifies the record: an external ID field you create
yourself, or a standard indexed field. Contact.Email is indexed, so it works as a
key:
Contact record = new Contact(Email = 'taylor@brightlabs.io', LastName = 'Taylor');
Database.UpsertResult upsertResult = Database.upsert(record, Contact.Email);
if (upsertResult.isCreated()) {
// there was no Contact with that Email, so one was inserted
} else {
// a Contact with that Email existed, and it was updated
}
The second argument is the key. Salesforce matches the incoming record against the
existing rows on that field, and isCreated() tells you which side of the match it
landed on.
Not every field can be a key. The key has to be an external ID custom field or a
standard indexed field, and the error says so plainly:
Invalid field for upsert, must be an External Id custom or standard indexed field: Name
Look closely at the code above: the variable is called record, not contact. That is
not a style choice. If you write Contact contact = new Contact(...), the name
Contact inside the method now refers to that VARIABLE, not to the type, so
Contact.Email reads as the Email of the record you just built. The compiler goes
looking for an overload that takes a String there and reports:
Method does not exist or incorrect signature: void upsert(Contact, String) from the type Database
The message names a method signature that does not exist, which sends you hunting
through the Database API instead of looking at your own variable name. Name the
variable something else, or use a fully qualified reference.
An upsert behaves like an update on the fields it touches. A Contact that already has
a Phone and an Account keeps both when you upsert it with only an Email and a LastName
in the record. The fields you leave off the incoming record are left alone on the
existing row.
A bare upsert record; does NOT use your Email as a key. It has no key at all, so it
matches on the record Id, and a record with no Id is a new record:
Contact record = new Contact(Email = 'taylor@brightlabs.io', LastName = 'Taylor'); upsert record; // creates a SECOND contact with the same email
Probed on the validation org: after that runs, two Contacts share that email. Use the
two-argument Database.upsert(record, Contact.Email) form when the key is a field
other than the Id.
The key cannot be blank. Upserting a Contact whose Email is null, empty, or only
spaces throws:
Upsert failed. First exception on row 0; first error: MISSING_ARGUMENT, Email not specified: []
So guard the key before you upsert. LastName is required on Contact as well, so a
blank LastName fails the same way with REQUIRED_FIELD_MISSING.
The first mistake is the statement form, which quietly creates duplicates. The second
is naming the variable after the type, which turns the key reference into a compile
error about a method signature you never wrote. The third is guarding the key but not
the required fields.
Upsert is how an integration stays idempotent: run the same batch twice and the
second run changes nothing.