DEV Community

Richard C
Richard C

Posted on AI-assisted

How to find orphan Contacts after a Salesforce migration (AccountId is blank)

AI-Assisted

By Richard C, AIL Studio

After a Salesforce migration, a Contact can keep every field value and still have no Account. That blank AccountId is a different problem from a wrong parent. An orphan Contact has no Account. A wrong-parent Contact still has an AccountId, but it points at the wrong company — covered in How to catch wrong-parent Contacts after a Salesforce migration and the SOQL parent audit in How to Audit Contact–Account Parents After a Salesforce Migration with SOQL.

Blank AccountId also changes who can see the record. Per Salesforce Help, Contacts not linked to an Account are private: only the owner and administrators with View/Modify All Data can see them, and Contact sharing rules do not apply. So an orphan query is a candidate review population for go-live — not automatic proof the migration failed, and not a list you should mass-attach without reading each row.

1. Why orphans show up after a load

Example causes (not exhaustive): Accounts loaded after Contacts; the AccountId map missed rows; the source already had private Contacts; a parent lookup failed on insert. Salesforce’s migration guidance stresses importing related objects in a workable order and replacing legacy IDs so relationships reattach — process context, not a diagnosis of your specific file.

A blank AccountId in the target is one finding. Decide whether that Contact should stay private, attach to a known Account, or need investigation before anyone rewrites data.

2. Find them with SOQL

Use null in the WHERE clause for the lookup. Salesforce’s SOQL null guide shows searching with the null keyword; the boolean-null quirk (null matching FALSE) applies to boolean fields, not to this Account lookup Id check.

SELECT Id, FirstName, LastName, Email, OwnerId, CreatedDate
FROM Contact
WHERE AccountId = null
Enter fullscreen mode Exit fullscreen mode

Optional count:

SELECT COUNT()
FROM Contact
WHERE AccountId = null
Enter fullscreen mode Exit fullscreen mode

To limit the count to Contacts that look migration-loaded, add your Contact External ID field (placeholder example: Legacy_Id__c — replace with your own External ID field; not every org has one):

SELECT COUNT()
FROM Contact
WHERE AccountId = null AND Legacy_Id__c != null
Enter fullscreen mode Exit fullscreen mode

Compare that count with the source system's count of Contacts with no account, so long-standing private Contacts aren't mistaken for load failures.

Run this in a tool you already have permission to use (Developer Console, VS Code, Workbench, or your approved admin path). No Salesforce org was queried for this article; treat any sample rows below as Example data.

3. Export or report the same set

If you prefer files, export Contacts with Data Loader including Id, name, Email, OwnerId, CreatedDate and AccountId, then keep rows where AccountId is blank. A standard Contacts report filtered to Account blank can produce the same candidate list for review. Label the export Example data until it is your real org output.

Keep Contact Id in the file. Prefer matching on Contact Id for any later update. Do not invent Account Ids in the sheet.

If your export tool turns blank lookups into empty strings, treat those empties as the same candidate set as SOQL null for this review. Confirm column headers before you filter: an Account name column is not AccountId. When owners differ across rows, include OwnerId so reviewers can see whether a private Contact was expected for that person.

4. Triage before you attach anything

Example data (not real Contacts or Accounts):

LastName Email Owner CreatedDate Decision Target Account external key Notes
Example, Renée renee@example.invalid Admin User 2026-11-01 Attach AC-001 Source had parent; map missed
Example, Sam sam@example.invalid Sales Rep 2026-10-28 Leave private Intentional private Contact
Example, Jo jo@example.invalid Admin User 2026-11-02 Investigate Email domain unclear

Optional tally (engine-agnostic arithmetic; not claimed as a Google Sheets test):

=COUNTIF(E2:E4,"Attach")
=COUNTIF(E2:E4,"Leave private")
=COUNTIF(E2:E4,"Investigate")
Enter fullscreen mode Exit fullscreen mode

Blank AccountId alone does not mean “attach to the first Account that looks similar.” Wrong AccountId creates a wrong-parent problem. Leave intentional private Contacts alone unless the business owner says otherwise.

Work the Investigate rows before Attach. A missing email, a shared mailbox, or a source note that said “no company” can all look like load defects when they are not. Record who approved Leave private so a later orphan re-query does not reopen settled decisions.

5. Attach safely when the decision is Attach

Salesforce Help documents a Data Import Wizard path to mass-update Account on existing Contacts, and states that a blank Account leaves the Contact private. Prefer Contact Salesforce Id for matching. Map Account by Salesforce Id or by an External ID your org already uses. Data Loader update on Contact Id with AccountId filled is the same idea: update only reviewed Attach rows.

Do not auto-attach the whole orphan export. Do not delete orphans to “clean” the count. After an Attach update, re-run the null query and spot-check a few Contact pages. If you attach the wrong Account, you have traded an orphan for a wrong parent — go back to the wrong-parent checks linked above.

6. Honest limits

  • Some Contacts are intentionally private; privacy after a blank Account is documented Salesforce behaviour, not automatically a defect.
  • Sharing rules do not surface private Contacts to ordinary users.
  • AccountId = null does not detect a non-null wrong parent.
  • Permissions matter: without View All Data (or ownership), you may under-count orphans you cannot see.
  • No org execution or fabricated import results are claimed here. Re-check against your firm’s go-live rules and sandbox tests.
  • The product line below is limited to wrong-parent CSV compare on shared stable keys. It does not detect blank AccountId orphans.

An orphan query catches a blank AccountId. It will not catch a Contact that still has an AccountId pointing at the wrong company — that needs a parent-key compare. If you want that wrong-parent check in one browser workbook, RecordBridge (US$49, one-time) compares before-and-after Account and Contact CSVs and flags those mismatches. It does not cancel loads or replace your firm's go-live sign-off.

By Richard C, AIL Studio

Top comments (0)