There are several issues with this code (you should never1 have queries inside of a loop), but the fundamental problem you're running into is one of how relationships work.
The immediate error that you're facing, List has no rows for assignment to SObject
is because you're taking the result of a query (which always returns a list, even if it's an empty list) and putting it into a single SObject variable. Salesforce allows the SObject record = [some query];
syntax a "syntactic sugar". It's a nice quality-of-life thing when it works, but it requires that your query return exactly one row. 0 rows and >= 2 rows will cause issues.
The reason that your query is returning 0 rows is because your contact and account are not yet related to one another. If you're creating Accounts from a trigger on Contact, then it's impossible for you query based on Contact.AccountId
in your Account
trigger.
You can't relate two records until they both have Ids. Based on your description, I imagine you're doing something like
List<Account> accountsToInsert = new List<Account>();
for(Contact c :trigger.new){
if(c.Lead_Status__c == 'Converted'){
accountsToInsert.add(new Account());
}
}
insert accountsToInsert;
Your Contacts have Ids, and you're correct in thinking that the Accounts will be assigned Ids by the time you get into your trigger after insert
, but there's not yet any correlation between the two. Unless you have another way to tie a newly inserted account to an existing lead, a trigger on Account
is not the right approach to take.
In general, the responsibility for populating relationship fields falls to the code that causes the DML for the related record. Here, that would be my example code (which could be part of an update trigger on Contact).
Expanding on that example
// Best practice is to only have one trigger per SObject
// Also best practice, using a trigger framework and keeping the trigger itself free of logic.
// That's off-topic for this question, and is covered in plenty of other questions
trigger Contact on Contact(before update){
// Using a map here will make it easy to relate the Contact to the Account
// after we're done with the DML
Map<Id, Account> contactIdToAccount = new Map<Id, Account>();
// Step 1 is creating the accounts
for(Contact c :trigger.new){
// skip processing records that aren't converted, or haven't changed to converted
if(c.Lead_Status__c != 'Converted' || trigger.oldMap.get(c.Id).Lead_Status__c == c.Lead_Status__c){
continue;
}
contactIdToAccount.put(c.Id, new Account(
// set account fieldName = value pairs here
);
}
// DML is another thing that should never appear in a loop
insert contactIdToAccount.values();
// Step 2 is to relate the newly created Accounts to their Contacts
// Looping over trigger.new again so we can take advantage of the "before" trigger
// to avoid DML
for(Contact c :trigger.new){
c.AccountId = contactIdToAccount.get(c.Id).Id;
}
}
The catch there being that you'd need to wait until after update
on Contact before you could query for Contacts based on the Account Id (or have a static variable defined in an Apex class (not a trigger) to hold that correlation for you).
In the end, this strikes me as work better handled completely by a trigger on Contact
rather than a trigger on Account
.
1: Ok, there are a few, very specific situations where it is ok, but you need an astoundingly firm grasp on what you're doing, why you're doing it, and why the query belongs in the loop. This is definitely not one of those situations