Salesforce DML Operations
SOQL reads data. DML changes data. DML stands for Data Manipulation Language, and Apex uses it to insert, update, delete, and restore records in the Salesforce database. A developer who knows DML can build any automation that creates or modifies records, from a simple welcome task to a bulk data cleanup.
The Core DML Statements
| Statement | Action |
|---|---|
| insert | Creates new records |
| update | Changes existing records |
| upsert | Updates a record when it exists and inserts it when it does not |
| delete | Moves records to the Recycle Bin |
| undelete | Restores records from the Recycle Bin |
| merge | Combines up to three records of the same type into one |
Insert: Create a Record
Account acc = new Account();
acc.Name = 'Sunrise Traders';
acc.Industry = 'Retail';
insert acc;
System.debug(acc.Id); // Salesforce fills the Id after insert
Salesforce generates the Id during insert and places the value back into the variable. Related records use that Id right away.
Contact con = new Contact(
LastName = 'Verma',
AccountId = acc.Id
);
insert con;
Update: Change a Record
An update needs a record that carries an Id. Query the record, change the field, and save.
Account acc = [SELECT Id, Rating FROM Account WHERE Name = 'Sunrise Traders' LIMIT 1];
acc.Rating = 'Hot';
update acc;
Upsert: Update or Insert
Upsert uses a matching field to decide the action. The field is either the record Id or an external Id field.
Account acc = new Account(Name = 'Sunrise Traders', AccountNumber = 'A-100');
upsert acc AccountNumber;
Data integrations rely on upsert because the external system does not know Salesforce Ids.
Delete and Undelete
delete acc; // record moves to the Recycle Bin
undelete acc; // record returns with its relationships
Always Use Lists for Bulk DML
A trigger can receive up to 200 records at once. Running one DML statement per record exhausts platform limits quickly. Collect records in a List and run a single statement.
Bad pattern Good pattern
----------- ------------
Loop 200 records Loop 200 records
insert one record add record to List
(200 DML statements) insert whole List
(1 DML statement)
List<Task> tasks = new List<Task>();
for (Account a : accounts) {
tasks.add(new Task(Subject = 'Call customer', WhatId = a.Id));
}
insert tasks;
Database Methods: Partial Success
A DML statement fails as a whole when one record has a problem. Database methods offer more control. Passing false for the allOrNone parameter lets good records save while bad records report their errors.
List<Database.SaveResult> results = Database.insert(contactList, false);
for (Database.SaveResult sr : results) {
if (!sr.isSuccess()) {
for (Database.Error err : sr.getErrors()) {
System.debug(err.getMessage());
}
}
}
List of 5 contacts | Database.insert(list, false) | +-- Contact 1 saved +-- Contact 2 saved +-- Contact 3 FAILED (error reported) +-- Contact 4 saved +-- Contact 5 saved
Handling Errors With Try and Catch
try {
insert acc;
} catch (DmlException e) {
System.debug('Save failed: ' + e.getMessage());
}
A DML exception gives the failing row and the reason. The catch block can log the problem or show a friendly message to the user.
Governor Limits for DML
Salesforce allows 150 DML statements and 10,000 processed rows in one transaction. Bulk-safe code stays far below both numbers.
Mixed DML Error
Setup objects, such as User and Group, and regular objects, such as Account, cannot change in the same transaction. Moving the setup work to an asynchronous method solves the conflict.
Example Scenario: Welcome Task for New Accounts
trigger AccountWelcome on Account (after insert) {
List<Task> tasks = new List<Task>();
for (Account a : Trigger.new) {
tasks.add(new Task(
Subject = 'Send welcome kit',
WhatId = a.Id,
OwnerId = a.OwnerId
));
}
insert tasks;
}
The trigger creates one task for each new account and runs a single DML statement regardless of the batch size.
DML in Triggers and Flows
Triggers and flows share one transaction when a user saves a record. A trigger that fires after an insert can change other records through DML, and those changes can fire further triggers. Salesforce counts every DML statement across the whole chain against the same limit of 150. Developers who understand the chain design triggers that do one clear job and avoid hidden loops.
Recursion deserves a guard. A trigger that updates a record can start itself again through that update. A static Boolean variable in a helper class records that the trigger already ran and stops the second pass.
Reading Results After DML
After an insert, the new record carries its Id. After an update, the record keeps the same Id and shows the new values. Developers read the Id to link related records or to return a link to the user. A Database.UpsertResult tells you whether Salesforce created a new record or updated an existing one, through the isCreated method.
Best Practices
- Never place DML inside a loop.
- Check that a record has an Id before an update.
- Use Database methods when partial success makes sense.
- Write try and catch blocks around DML that users trigger.
- Test every DML path with bulk data in a test class.
