I think there are two issues to address here.
Issue 1, query selectivity
You'll want to make your query on Account
more selective. Adding filters for FirstName
, LastName
, and PersonEmail
into your query should do the trick. Just create one set for each, and populate while you iterate over the Cases.
This won't get you 100% of the way to your goal, but it does narrow down the number of records you need to try to search against. The thing with using multiple filters and binding collections to them is that the filter you're building is effectively the cartesian product of your collections.
firstNames = {'Derek', 'Erik'}
and lastNames = {'F', 'Smith'}
means you'll get records for "Derek F", "Erik F", "Derek Smith", and "Erik Smith" (if they exist).
Issue 2, what am I searching for?
With the information you've given so far, you don't care so much about the Id of an Account
that matches your first/last/email combo. Rather, you care whether or not there is a match at all.
So, instead of a Map<Id, String>
from Account
to the concatenation of your fields, I'd suggest a Set<String>
to simply store the concatenation itself. This will make your search lightning fast (given this case, was there any account that matched firstname + lastname + email?)
Putting it together
The pseudo-code might look like this
declare sets to hold firstname, lastname, and email (one each)
for(my cases){
add the firstname to the firstname set
add the lastname to the lastname set
add the email to the email set
}
declare a set to hold the concatenation of firstname, lastname, and email from queried accounts
for([SELECT fields FROM Account WHERE firstname IN :firstnameset AND lastname IN :lastnameset AND email IN emailset]){
add the concatenation into the set we just declared
}
for(my cases){
String needle = concatenatation of your case fields;
if(existingAccountSet.contains(needle)){
// no need to make an account
}else{
// make an account
}
}
Bonus
For relatively short and simple things like this, concatenating values into a single string should do the job just fine.
Another way to do this would be to use an SObject
as a composite key. For example...
Set<Account> needleAccounts = new Set<Account>();
for(my cases){
needleAccounts.add(new Account(
FirstName = case.firstnameField,
LastName = case.lastnameField,
PersonEmail = case.emailField
));
}
Normally, using an SObject as the datatype of a set, or the key of a map is discouraged (because if you change anything on the object instance in a set or the key of a map, you'll lose access to that data in the collection). If you never alter things after placing them into a set/map though (or are careful about using .clone()
), then it can be fine.
The advantage here is twofold:
- It doesn't matter what order you use for the "keys" of your composite object (unlike a concatenated string)
- If it turns out that you need to insert a new Account, you already have a head start on creating that record
You could also eliminate the third loop by using the removeAll()
method of the Set class.
declare 3 sets for your fname, lname, email
Set<Account> needleAccounts = new Set<Account>();
for(my cases){
needleAccounts.add(new Account(
FirstName = case.firstnameField,
LastName = case.lastnameField,
PersonEmail = case.emailField
));
populate your 3 other sets (as in the other example)
}
Set<Account> haystackAccounts = new Set<Account>();
for([SELECT fields FROM Account WHERE firstname IN :firstnameset AND lastname IN :lastnameset AND email IN emailset]){
haystackAccounts.add(new Account(
FirstName = account.firstnameField,
LastName = account.lastnameField,
PersonEmail = account.emailField
));
}
needleAccounts.removeAll(haystackAccounts);
// Now, needleAccounts will only contain Accounts that need to be created
insert needleAccounts;