Upsert depends upon the value of External ID
fields being unique. If you choose to not make External ID
unique, you cannot leverage this feature. This means that you would need to leverage external logic in order to perform the upsert operations. Either clean up your data, or use external logic. One potential solution could be to write a trigger to eliminate a previous External ID matching the record's value when it is inserted or updated; you'll still need to clean up data that existed before this trigger was implemented. Off the top of my head, your trigger might look like this:
trigger clearOldIds on Contact (after insert, after update) {
Set<String> newIds = new Set<String>(); // All new ID values
for(Contact record:Trigger.new)
newIds.add(record.External_Id__c);
newIds.remove(null); // Ignore blank values
Contact[] others = [SELECT Id FROM Contact WHERE External_Id__c IN :newIds AND Id NOT IN :Trigger.new];
for(Contact record:others) // Clear old IDs
record.External_ID__c = null;
update others;
}
This would probably work in most cases, but you'll need to clean up your data at least once for this to operate correctly.
Another solution would be to create a service that your external service could consume. Here's a primitive example that would cover this:
global with sharing class SystemUtils {
global webservice void upsertLast(Contact[] records) {
Map<String, Id> uniqueValues = new Map<String, Id>();
Contact[] newRecords = new Contact[0], oldRecords = new Contact[0];
for(Contact record:records)
uniqueValues.put(record.External_ID__c,null);
for(Contact record:[SELECT Id,External_ID__c FROM Contact WHERE External_Id__c IN :uniqueValues.keySet() ORDER BY SystemModStamp ASC])
uniqueValues.put(record.External_Id__c, record.Id);
for(Contact record:records)
record.Id = uniqueValues.get(record.External_Id__c);
for(Contact record:records)
(record.Id==null?newRecords:oldRecords).add(record);
insert newRecords;
update oldRecords;
}
}
I haven't implemented error checking, that'd be the last step, but this sort of code would conceivably work assuming there were no errors.
Yes, those fields can have a NULL value but wont serve the purpose.
External ID:
This is a field that usually references an ID from another (external) system. For instance, if the customer has an Oracle Financials system that they will be linking with salesforce.com, it may be easier for them to be able to refer to the Oracle ID of account records from within salesforce. So they would create an external ID in salesforce.com and they would load the Oracle ID into that field for each account. They can then refer to that ID field, rather than the salesforce.com id.
Additionally, if you have an external ID field, the field becomes searchable in the sidebar search. You also can use the upsert API call with the extenal ID to refer to records.
You can have multiple records with the same external ID (though it is not reccomended, as it will defeat the purpose of the external id)
Unique ID field
This is a setting for the field that will prevent you from using the same value in multiple records for the unique field. So if I create a 5 character text field and make it unique, and I create a record with the value "12345" I will not be able to create another record with that same value in the unique field. If I try to do so, I will get an error saying that the value is already in use.
Often, External Ids are set with the unique property so that the IDs will be unique to each record.
Please refer to : https://help.salesforce.com/HTViewHelpDoc?id=custom_field_attributes.htm&language=en_US
Best Answer
Unfortunately you are right, using external IDs can lead to multiple matches during upsert operations.. taken from the documentation on Upserting examples:
So in answer to your question, yes you will get an error! Frustrating as it is.
Edit: This would need checking, but there is a specific exception type DmlException, which I imagine is what would be thrown, and you could (and probably should anyway) look to catch after an upsert call
Edit 2(!): Don't forget of course as well you can set your external ID to have unique (and case sensitive, and required) properties, which I would personally recommend if you ever intend on using the field in upsert commands.