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

StatementAction
insertCreates new records
updateChanges existing records
upsertUpdates a record when it exists and inserts it when it does not
deleteMoves records to the Recycle Bin
undeleteRestores records from the Recycle Bin
mergeCombines 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.