取引先作成時に履歴へ同名項目をコピーするApexトリガの書き方
オブジェクト構成を用意し、Database.insertとSaveResultで安全に履歴レコードを作るトリガ処理を説明します。
なぜトリガでコピーするのか
取引先の作成時と更新時に、その時点の値をカスタムオブジェクトへ複製して残したいことがあります。標準の項目履歴機能は文字数やオブジェクトの制約があり、任意の項目をまとめて複製する用途には向きません。Apexトリガでコピーすれば、取引先のカスタム項目が増えても取得項目を書き足すだけで済みます。
オブジェクトの構成
まず入れ物を用意します。
取引先履歴オブジェクトを作る
設定のオブジェクトマネージャで、カスタムオブジェクト
AccountHistory__cを作成します。取引先への参照項目を作る
AccountHistory__cに、取引先を指す参照関係項目Account__cを作成します。複製したい項目を同名で作る
取引先側のカスタム項目のうち、履歴として残したいものを、
AccountHistory__c側に同じAPI参照名で作成します。
⚠️ 同名でなければコピーは行われません。トリガは取引先側のカスタム項目名で、履歴側に同名の項目があるかどうかを見ています。
Apexトリガとハンドラー
after insertとafter updateで、作成・更新された取引先ぶんの履歴レコードをまとめて作ります。
trigger AccountTrigger on Account (after insert, after update) { if (Trigger.isAfter && (Trigger.isInsert || Trigger.isUpdate)) { new AccountTriggerHandler().afterUpsert(Trigger.new); } }
取引先の後処理をハンドラークラスへ委譲します。DMLはこの中に置きません。
public class AccountTriggerHandler { public void afterUpsert(List<Account> accounts) { List<AccountHistory__c> histories = buildHistories(accounts); if (histories.isEmpty()) { return; } Database.SaveResult[] results = Database.insert(histories, false); for (Database.SaveResult result : results) { if (!result.isSuccess()) { for (Database.Error err : result.getErrors()) { System.debug(LoggingLevel.ERROR, '取引先履歴の作成に失敗: ' + err.getMessage()); } } } } private List<AccountHistory__c> buildHistories(List<Account> accounts) { Map<String, Schema.SObjectField> sourceFields = Account.SObjectType.getDescribe().fields.getMap(); Map<String, Schema.SObjectField> targetFields = AccountHistory__c.SObjectType.getDescribe().fields.getMap(); List<String> copyFieldNames = new List<String>(); for (String fieldName : sourceFields.keySet()) { if (sourceFields.get(fieldName).getDescribe().isCustom() && targetFields.containsKey(fieldName)) { copyFieldNames.add(fieldName); } } List<AccountHistory__c> histories = new List<AccountHistory__c>(); for (Account acc : accounts) { AccountHistory__c history = new AccountHistory__c(); for (String fieldName : copyFieldNames) { history.put(fieldName, acc.get(fieldName)); } history.Name = acc.Name; history.Account__c = acc.Id; histories.add(history); } return histories; } }
履歴レコードをまとめて作ります。Schemaの取得はループの外で1回だけ行います。
避けたい書き方
このコードでは、次の3つの書き方を避けています。
| 避けたい書き方 | 避ける理由 |
|---|---|
upsert upsertAccHistoryList Account.Id; | 参照型の項目は外部ID項目にできません。履歴は毎回新規作成でよいのでinsert(Database.insert)を使います |
Schema.describeSObjectsを取引先1件ごとに呼ぶ | 説明(Schema describe)はレコードの件数に関係なく同じ結果です。ループの外で1回だけ呼びます |
Database.setSavepointとtry-catchで失敗を握りつぶす | Database.insert(list, false)を使えば、SaveResultで失敗したレコードだけを個別に確認できます。取引先本体の登録を巻き込みません |
⚠️ insertやupsert(allOrNoneを指定しない形)を使うと、履歴の作成に1件でも失敗したとき、取引先の作成・更新そのものが例外で止まります。afterトリガの中のDML例外は、同じトランザクションの中にある取引先の変更も巻き込んでロールバックします。付随的に作る履歴レコードは、Database.insert(list, false)で失敗を許容し、SaveResultを見て記録するだけにするのが安全です。
同じトランザクションで2回走らせない
このトリガは取引先自身を更新しないので、いま書いた形では再帰は起きません。ただし、同じafterトリガの中で取引先側の項目も更新するように直すと、その更新が同じトリガをもう一度呼び出し、再帰実行になります。静的変数で「このトランザクションではもう実行した」を覚えておくのが定番の防ぎ方です。
public class AccountTriggerHandler { private static Boolean hasRun = false; public void afterUpsert(List<Account> accounts) { if (hasRun) { return; } hasRun = true; } }
クラス変数はトランザクションの間だけ生きています。2回目の呼び出しをここで止めます。
動作確認
取引先を新規作成する
カスタム項目に値を入れて保存します。
取引先履歴を確認する
AccountHistory__cのタブか関連リストから、直後に1件作られていることを確認します。取引先側のカスタム項目の値が、同名の項目へ複製されているかを見ます。取引先を更新する
複製対象の項目を書き換えて保存します。
更新後の履歴を確認する
更新のたびに履歴レコードが追加されることを確認します。既存の履歴が上書きされないことも見ます。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
参照型の項目を外部ID指定にしてupsertする | コンパイルも実行もできません。外部IDにできるのはテキスト・数値・メールなど値型の項目だけです |
Schema.describeSObjectsをレコードごとに呼ぶ | 結果は同じなのに毎回呼ぶだけ無駄です。件数が増えると素直に遅くなります |
afterトリガの中のDMLにallOrNoneを指定しない | 付随的な処理の失敗が、本体のレコード操作ごと巻き戻します |
| 同名でない項目を複製対象だと思い込む | このコードは項目名の一致だけを見ます。名前が違えばコピーされません |
確認した環境
- 2026年9月/Salesforce Summer '26(APIバージョン67.0)時点の公式リファレンスで、DML文とDatabaseクラスの違い、
allOrNoneとSaveResultの仕様を確認しています
まとめ
- 参照型の項目は外部IDにできません。
upsertで使う前に項目タイプを確認してください Database.insert(list, false)とSaveResultを使うと、付随的な処理の失敗を本体の操作から切り離せますSchema.describeSObjectsなどの重い呼び出しは、レコードごとではなくループの外で1回だけ行います- afterトリガが自分自身を再度呼び出す可能性があるときは、静的変数で2回目以降の実行を止めます
- コピーは項目名が完全一致しているときだけ働きます。取引先側に項目を増やしたら、履歴側にも同名で作成してください
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。