insert creates a record, update changes one, and delete removes it. Deleting is
the easiest of the three to write and the hardest to get right, because it is the only
one that can take other records with it, and the only one something else in the org can
refuse.
A delete works on a record Id, so the record has to have one. The usual shape is the
same one update uses: query the record, then delete what the query returned.
List<Contact> rows = [SELECT Id FROM Contact WHERE Email = :email]; delete rows;
The query hands you records that already carry their Ids, so there is nothing else to
prepare. Delete the records the query returned, not their Ids: delete cannot take a
List<Id> and fails to compile with DML requires SObject or SObject list type.
If you delete a record you built yourself and never inserted, the Id is null, and the
delete fails:
System.ListException: Invalid id at index 0: null
Note the exception type. It is a ListException, not a DmlException, so a
catch (DmlException e) block does NOT catch it. Only a general catch (Exception e)
does. Probed on the validation org: e instanceof DmlException is false and
e instanceof ListException is true.
A record that has an Id but does not exist in the database fails differently: that one
is a DmlException carrying INVALID_CROSS_REFERENCE_KEY, invalid cross reference id.
Sometimes the delete itself is fine and the org says no. The error looks like this:
DELETE_FAILED, Your attempt to delete Alpine Private Bank could not be completed because
it is associated with the following maintenance requests.: 00007635
That means something else is holding the record: a validation rule, a trigger, or a
child record that cannot go without it. This is worth knowing before you build
something around a delete. On the production grading org, an Account trigger creates a
Case for every new Account, and that Case blocks the Account delete — and because
Case cannot be queried there, it cannot be removed first either. On that same org a
Contact deletes without any trouble.
So a delete that works on your sandbox may not work on the org you are graded in. Test
the delete on the object you actually intend to delete.
Deleting an Account also deletes the Contacts and Opportunities that pointed at it.
Those are lookup relationships, and the child rows go with the parent. Probed on the
validation org: deleting an Account that had one Contact and one Opportunity left zero
of each behind.
This is the reason to think before you delete: the rows you remove are not only the
row you asked about.
Database.delete(record) does the same thing as the statement and hands you a
Database.DeleteResult with isSuccess() and getErrors(). Pass false as the
second argument to keep going when one row fails:
List<Database.DeleteResult> results = Database.delete(rows, false);
With false the good rows are deleted and the failures come back in the result.
Without it, one bad row rolls back the whole statement. Note the return type: the
single-record form returns one Database.DeleteResult, while the list form returns a
List<Database.DeleteResult>.
The first mistake is deleting a record you never inserted, which throws the
ListException above. The second is catching DmlException and assuming that covers
it. The third is deleting a parent record without knowing that its children go with
it. A fourth, quieter one: a Contact Email is not unique on this org, so deleting by
email can remove more than one row.
A delete is the only DML operation you cannot undo by changing the record back. Soft
delete exists for exactly this reason: it hides the row instead of removing it, so a
mistake can be reversed.