Skip to content

01 - Data Model and SOQL/SOSL

Object model

Salesforce has standard objects (Account, Contact, etc.) and custom objects (__c suffix). Both behave the same way to Apex.

sObject in Apex

Account a = new Account(Name='Acme');
insert a;
System.debug(a.Id);  // populated after insert

// Generic sObject
sObject s = a;
String name = (String) s.get('Name');

Field references

  • Direct: account.Name
  • Custom: account.MyField__c (note __c suffix)
  • Relationship: account.Owner.Name (one level up)
  • Custom relationships: account.MyParent__r.Name (__r for relationship suffix)

SOQL (Salesforce Object Query Language)

SQL-like, type-safe in Apex.

Basic queries

// Static query
List<Account> accs = [SELECT Id, Name, Industry FROM Account
                       WHERE Industry = 'Technology'
                       LIMIT 10];

// Bind variables (preferred over string concat)
String industry = 'Technology';
List<Account> accs2 = [SELECT Id FROM Account WHERE Industry = :industry];

// Dynamic SOQL (string)
String query = 'SELECT Id, Name FROM Account WHERE Industry = \'' + industry + '\'';
List<Account> accs3 = Database.query(query);  // SOQL injection risk - sanitize input!

Relationships in SOQL

Child-to-parent (dot notation)

SELECT Id, Name, Account.Name, Account.Industry FROM Contact WHERE Account.Industry = 'Tech'

Parent-to-child (subquery)

SELECT Id, Name, (SELECT Id, FirstName FROM Contacts) FROM Account

For custom relationships, use Children__r (__r is the relationship API name):

SELECT Id, (SELECT Id FROM My_Children__r) FROM My_Parent__c

WHERE clauses

WHERE Industry = 'Technology'
WHERE Industry IN ('Technology', 'Finance')
WHERE Name LIKE 'Acme%'
WHERE CreatedDate = LAST_N_DAYS:30
WHERE CreatedDate = LAST_QUARTER
WHERE CloseDate > TODAY
WHERE Active__c = TRUE
WHERE Industry = NULL  -- match nulls

Ordering and limiting

ORDER BY Name ASC, CreatedDate DESC NULLS LAST
LIMIT 50
OFFSET 100  -- max OFFSET is 2000

Aggregate queries

SELECT Industry, COUNT(Id), AVG(AnnualRevenue) total
FROM Account
GROUP BY Industry
HAVING COUNT(Id) > 10

Returns AggregateResult records:

for (AggregateResult ar : [SELECT Industry, COUNT(Id) cnt FROM Account GROUP BY Industry]) {
    System.debug(ar.get('Industry') + ': ' + ar.get('cnt'));
}

Date / DateTime functions

  • CALENDAR_YEAR(CloseDate), CALENDAR_MONTH(CloseDate), CALENDAR_QUARTER(CloseDate)
  • FISCAL_YEAR(), FISCAL_MONTH(), FISCAL_QUARTER()
  • DAY_ONLY() - extract date from datetime

TYPEOF (polymorphic)

For polymorphic fields like Owner or Who/What on Task:

SELECT TYPEOF Owner WHEN User THEN Username, Email WHEN Group THEN Name END FROM Lead

FOR VIEW / FOR REFERENCE

Update RecentlyViewed table when querying:

SELECT Id FROM Account FOR VIEW

FOR UPDATE

Lock records during a transaction (prevent concurrent updates):

SELECT Id FROM Account WHERE Id = :accId FOR UPDATE

SOQL governor limits

  • 100 queries per sync transaction (200 async)
  • 50,000 records per query
  • Use Database.QueryLocator (Batch Apex) for >50K

SOSL (Salesforce Object Search Language)

Multi-object full-text search:

List<List<sObject>> results = [FIND 'Acme*' IN ALL FIELDS
                                RETURNING Account(Name, Industry),
                                          Contact(LastName, Email),
                                          Lead(LastName)];

List<Account> accs = (List<Account>) results[0];
List<Contact> contacts = (List<Contact>) results[1];

When to use SOSL vs SOQL

  • SOQL - know the object, want filtered results
  • SOSL - keyword search across multiple objects

SOSL is indexed-search backed; faster than SOQL for full-text matching but doesn't support all WHERE-style filters.


sObject collections

List

List<Account> accs = new List<Account>();
accs.add(a1);
for (Account a : accs) { }
Account first = accs[0];
Integer size = accs.size();

Set

Set<Id> accountIds = new Set<Id>();
for (Account a : accs) {
    accountIds.add(a.Id);
}

Map

Map<Id, Account> accMap = new Map<Id, Account>(accs);  // builds Map keyed by Id
Account a = accMap.get(someId);

// Map of Id to children
Map<Id, List<Contact>> contactsByAccountId = new Map<Id, List<Contact>>();
for (Contact c : contacts) {
    if (!contactsByAccountId.containsKey(c.AccountId)) {
        contactsByAccountId.put(c.AccountId, new List<Contact>());
    }
    contactsByAccountId.get(c.AccountId).add(c);
}

Schema metadata in Apex

Schema.SObjectType type = Account.sObjectType;
Schema.DescribeSObjectResult describe = type.getDescribe();
Map<String, Schema.SObjectField> fields = describe.fields.getMap();
Schema.DescribeFieldResult fieldDescribe = fields.get('Industry').getDescribe();
List<Schema.PicklistEntry> entries = fieldDescribe.getPicklistValues();

Useful for dynamic Apex that adapts to schema changes.


Common exam triggers

  • "Query Accounts and their related Contacts in one go" β†’ Subquery: SELECT Id, (SELECT Id FROM Contacts) FROM Account
  • "Reference parent Account from Contact" β†’ Dot: SELECT AccountId, Account.Name FROM Contact
  • "Avoid SOQL governor limit on >50K records" β†’ Batch Apex with QueryLocator
  • "Search across Account/Contact/Lead by keyword" β†’ SOSL
  • "Aggregate sum of revenue by Industry" β†’ SELECT Industry, SUM(AnnualRevenue) FROM Account GROUP BY Industry
  • "Bulkify trigger" β†’ Use Maps and Sets to gather IDs, do single SOQL outside loop
  • "Lock records to prevent concurrent updates" β†’ SELECT ... FOR UPDATE
  • "Dynamic field reference" β†’ Schema.getGlobalDescribe / DescribeSObjectResult