DML Operations & Database Methods

0/9 in this phase0/41 across the roadmap

📖 Concept

DML (Data Manipulation Language) operations are how you create, update, and delete records in Salesforce. Understanding the nuances between DML statements and Database methods is essential.

DML Statements (simple):

insert records;      — Create new records
update records;      — Modify existing records
upsert records;      — Insert or update (based on external ID or record ID)
delete records;      — Soft delete (goes to Recycle Bin)
undelete records;    — Restore from Recycle Bin
merge records;       — Merge duplicate records

Database Methods (advanced):

Database.insert(records, allOrNone)
Database.update(records, allOrNone)
Database.upsert(records, externalIdField, allOrNone)
Database.delete(records, allOrNone)
Database.undelete(records, allOrNone)

Key difference — allOrNone parameter:

  • DML statements: All or nothing (one failure rolls back everything)
  • Database methods with allOrNone = false: Partial success (successful records are committed, failed ones return errors)

Governor limits for DML:

  • 150 DML statements per transaction
  • 10,000 total DML rows per transaction
  • These are cumulative across all operations in the transaction

Upsert — the powerful hybrid: Upsert checks if the record exists:

  • If it has an Id → update
  • If it doesn't → insert
  • Can also match on External ID fields (useful for integration)

Best practices:

  1. Collect all records in a List, then perform ONE DML operation
  2. Use Database methods with allOrNone = false for bulk operations where partial success is acceptable
  3. Always handle DmlException in try-catch blocks
  4. Use Database.SaveResult to check individual record outcomes
  5. Consider using Database.setSavepoint() and Database.rollback() for transaction control

💻 Code Example

codeTap to expand ⛶
1// DML Operations — Complete Guide
2
3public class DMLExamples {
4
5 // 1. Basic DML statements
6 public static void basicDML() {
7 // INSERT
8 Account acc = new Account(Name = 'Acme Corp', Industry = 'Technology');
9 insert acc;
10 System.debug('New Account Id: ' + acc.Id); // Id populated after insert
11
12 // UPDATE
13 acc.AnnualRevenue = 5000000;
14 update acc;
15
16 // UPSERT — Insert or Update based on External ID
17 Account upsertAcc = new Account(
18 External_Id__c = 'EXT-001', // External ID field
19 Name = 'Upserted Account',
20 Industry = 'Finance'
21 );
22 upsert upsertAcc External_Id__c; // Match on External_Id__c
23
24 // DELETE (soft delete — goes to Recycle Bin)
25 delete acc;
26
27 // UNDELETE (restore from Recycle Bin)
28 undelete acc;
29 }
30
31 // 2. Bulk DML (proper pattern)
32 public static void bulkDML(List<Contact> newContacts) {
33 // GOOD — Single DML for entire list
34 insert newContacts; // Handles up to 10,000 records
35
36 // BAD — DML inside loop (hits 150 DML limit at 150 records)
37 // for (Contact c : newContacts) {
38 // insert c; // ❌ Each iteration is 1 DML statement!
39 // }
40 }
41
42 // 3. Database methods with partial success
43 public static void partialSuccessDML(List<Account> accounts) {
44 // allOrNone = false: successful records are saved,
45 // failed records return errors
46 Database.SaveResult[] results = Database.insert(accounts, false);
47
48 List<Account> successRecords = new List<Account>();
49 List<String> errorMessages = new List<String>();
50
51 for (Integer i = 0; i < results.size(); i++) {
52 Database.SaveResult sr = results[i];
53 if (sr.isSuccess()) {
54 successRecords.add(accounts[i]);
55 } else {
56 for (Database.Error err : sr.getErrors()) {
57 errorMessages.add(
58 'Record [' + i + '] ' + accounts[i].Name +
59 ': ' + err.getMessage() +
60 ' (Fields: ' + err.getFields() + ')'
61 );
62 }
63 }
64 }
65
66 System.debug('Inserted: ' + successRecords.size());
67 System.debug('Errors: ' + errorMessages.size());
68 for (String msg : errorMessages) {
69 System.debug('ERROR: ' + msg);
70 }
71 }
72
73 // 4. Upsert with External ID (integration pattern)
74 public static void upsertFromExternalSystem(List<Map<String, Object>> externalData) {
75 List<Account> accountsToUpsert = new List<Account>();
76
77 for (Map<String, Object> data : externalData) {
78 accountsToUpsert.add(new Account(
79 External_Id__c = (String) data.get('externalId'),
80 Name = (String) data.get('name'),
81 Industry = (String) data.get('industry'),
82 AnnualRevenue = (Decimal) data.get('revenue')
83 ));
84 }
85
86 // Upsert matches on External_Id__c
87 // - If External_Id__c exists → UPDATE
88 // - If External_Id__c doesn't exist → INSERT
89 Database.UpsertResult[] results = Database.upsert(
90 accountsToUpsert, Account.External_Id__c, false
91 );
92
93 for (Database.UpsertResult ur : results) {
94 if (ur.isSuccess()) {
95 if (ur.isCreated()) {
96 System.debug('INSERTED: ' + ur.getId());
97 } else {
98 System.debug('UPDATED: ' + ur.getId());
99 }
100 }
101 }
102 }
103
104 // 5. Transaction control with Savepoints
105 public static void transactionControl() {
106 Savepoint sp = Database.setSavepoint();
107
108 try {
109 Account acc = new Account(Name = 'Test Transaction');
110 insert acc;
111
112 Contact con = new Contact(
113 FirstName = 'Test',
114 LastName = 'Contact',
115 AccountId = acc.Id
116 );
117 insert con;
118
119 // Simulate a failure
120 if (someCondition) {
121 throw new CustomException('Business rule violated');
122 }
123
124 // If we get here, both records are committed
125 } catch (Exception e) {
126 // Rollback BOTH insert operations
127 Database.rollback(sp);
128 System.debug('Transaction rolled back: ' + e.getMessage());
129 }
130 }
131
132 // 6. Merge — Combine duplicate records
133 public static void mergeDuplicates(Id masterAccountId, Id duplicateAccountId) {
134 Account master = [SELECT Id FROM Account WHERE Id = :masterAccountId];
135 Account duplicate = [SELECT Id FROM Account WHERE Id = :duplicateAccountId];
136
137 // Merge transfers all child records (Contacts, Opportunities, etc.)
138 // from duplicate to master, then deletes the duplicate
139 merge master duplicate;
140
141 // Can merge up to 3 records at once
142 // merge master new List<Account>{dup1, dup2};
143 }
144
145 // 7. Mixed DML — System and Setup objects
146 // You CANNOT mix DML on setup objects (User, Profile, PermissionSet)
147 // with non-setup objects (Account, Contact) in the same transaction
148 public static void handleMixedDML() {
149 // This would FAIL:
150 // Account acc = new Account(Name = 'Test');
151 // insert acc;
152 // User u = [SELECT Id FROM User LIMIT 1];
153 // u.LastName = 'Updated';
154 // update u; // ❌ MIXED_DML_OPERATION error!
155
156 // SOLUTION: Use @future or System.runAs()
157 // Or separate the operations into different transactions
158 }
159}

🏋️ Practice Exercise

DML Practice Exercises:

  1. Write a method that inserts 500 Account records in a single DML operation and verify the count
  2. Implement a partial-success insert that logs all errors to a custom Error_Log__c object
  3. Use Database.setSavepoint() to implement a multi-step operation with rollback on any failure
  4. Write an upsert operation that syncs data from an external system using External ID
  5. Implement a merge operation that combines 3 duplicate Contacts into one master record
  6. Handle the Mixed DML Operation error — insert a User and an Account in the same test method
  7. Write a method that deletes records and then undeletes them, verifying data integrity
  8. Create a utility that performs bulk updates in chunks of 200 to handle lists larger than 10,000
  9. Write error handling that captures Database.Error details and creates human-readable error messages
  10. Implement an "undo" feature using Savepoints that rolls back the last 3 operations

⚠️ Common Mistakes

  • Performing DML inside loops — 'insert record;' inside a for-loop hits the 150 DML statement limit. Always collect records in a List and do one DML outside the loop

  • Not handling DML exceptions — a single bad record in 'insert records;' rolls back ALL records. Use Database.insert(records, false) for partial success

  • Mixing setup and non-setup DML — inserting a User and an Account in the same transaction throws MIXED_DML_OPERATION. Separate them with @future or System.runAs()

  • Not checking Database.SaveResult — always iterate results to log errors, especially with allOrNone = false

  • Exceeding the 10,000 DML rows limit — if you need to update more than 10,000 records, use Batch Apex

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for DML Operations & Database Methods. Login to unlock this feature.