はじめに
今回は、Salesforceのオブジェクトの項目・データをCSVでサクッと取得するためにApexを書きます。
自分は、普段からSalesforceからレコードや、スキーマ情報をApexで取らずに
- API
- CLI
- SalesforceDX
等でサクッととっているのですが、今回学習のためにApexでやってみました。
今回動かすApexは、
とりあえず全項目を検索し、CSVに出力する作りにしているので
大量データのバックアップ等には向いていません
この記事でやること
- 以下のApexを作成
- オブジェクト名を指定することで全部の項目をSELECTする
- CSVとしてSalesforceのファイル>ライブラリに保存する
- 保存するもの
- 検索結果のレコード
- 検索した項目のAPI参照名とラベルのセット
- 保存するもの
- Apex実行後に保存されるファイル>ライブラリの作成
実行環境
- trailhead ハンズオン組織
- 利用するデータ ハンズオン組織のサンプルデータ
書いたコード
今回書いたコードはこちらです。
public with sharing class CsvBatch implements Database.Batchable<SObject>, Database.Stateful {
private String objectName;
private Map<String, String> fields;
public CsvBatch(String objectName) {
this.objectName = objectName;
}
private String objectCsvColumnHeader;
private String schemaCsvColumnHeader;
private List<String> objectCsvRowValues = new List<String>();
private List<String> schemaCsvRowValues = new List<String>();
public Database.QueryLocator start(Database.BatchableContext bc) {
fields = getFields(objectName);
String columns = String.join(new List<String>(this.fields.keySet()), ', ');
String query = 'SELECT ' + columns + ' FROM ' + objectName;
return Database.getQueryLocator(query);
}
public void execute(Database.BatchableContext bc, List<SObject> records) {
// 取得したレコードでCSVの行を作成
// 例) '0015i00000KcklhAAB',false,'Sample Account for Entitlements'
for (SObject record : records) {
List<String> row = new List<String>();
for (String filedName : fields.keySet()) {
if (record.get(filedName) != null) {
row.add(String.valueOf(record.get(filedName)).escapeCsv());
} else {
row.add('');
}
}
objectCsvRowValues.add(String.join(row, ','));
}
// 項目のAPI参照名、ラベルでCSVの行を作成
schemaCsvColumnHeader = String.join(
new List<String>{ 'DeveloperName', 'Name' },
', '
);
List<String> fieldNames = new List<String>();
for (String filedName : fields.keySet()) {
List<String> row = new List<String>();
row.add(filedName.escapeCsv());
row.add(fields.get(filedName).escapeCsv());
schemaCsvRowValues.add(String.join(row, ','));
fieldNames.add(filedName.escapeCsv());
}
// オブジェクトの項目名でCSVのヘッダーを作成
objectCsvColumnHeader = String.join(fieldNames, ', ');
}
public void finish(Database.BatchableContext bc) {
// 保存先ライブラリの情報取得
List<ContentWorkspace> libraries = [
SELECT Id, Name
FROM ContentWorkspace
WHERE Name = 'ApexResults'
LIMIT 1
];
if (libraries.isEmpty()) {
System.debug('保存先ライブラリーが見つかりません。');
} else {
List<ContentVersion> documents = new List<ContentVersion>();
// 項目のAPI参照名、ラベルのCSVを作成
String schemaCsvFile =
schemaCsvColumnHeader +
'\n' +
String.join(schemaCsvRowValues, '\n');
ContentVersion schemaDoc = new ContentVersion(
Title = objectName + '-Schema',
PathOnClient = objectName + '-Schema' + '.csv',
VersionData = Blob.valueOf(schemaCsvFile),
FirstPublishLocationId = libraries[0].Id
);
documents.add(schemaDoc);
// レコード情報をCSVに
String objectCsvFile =
objectCsvColumnHeader +
'\n' +
String.join(objectCsvRowValues, '\n');
ContentVersion objectDoc = new ContentVersion(
Title = objectName,
PathOnClient = objectName + '.csv',
VersionData = Blob.valueOf(objectCsvFile),
FirstPublishLocationId = libraries[0].Id
);
documents.add(objectDoc);
// ContentVersionにCSVのデータを保存
insert documents;
}
}
// 指定したオブジェクトの項目のAPI参照名を返す
// API参照名をキー、ラベルを値
private Map<String, String> getFields(String objectName) {
Map<String, String> fields = new Map<String, String>();
Schema.SObjectType objectType = Schema.getGlobalDescribe().get(objectName);
Schema.DescribeSObjectResult describeObject = objectType.getDescribe();
Map<String, Schema.SObjectField> schemaFields = describeObject.fields.getMap();
for (Schema.SObjectField schemaField : schemaFields.values()) {
fields.put(
schemaField.getDescribe().getName(),
schemaField.getDescribe().getLabel()
);
}
return fields;
}
}
以下はテストコードです。
CSVの中身までは検証していませんが、ファイルが生成されたかどうかだけチェックしています。
@isTest
public with sharing class CsvBatch_Test {
@testSetup
static void setup() {
// テスト用のレコードを生成
Contact contact = new Contact(LastName = 'Test Contact');
insert contact;
}
@isTest
static void CsvBatch_Test() {
Test.startTest();
CsvBatch batch = new CsvBatch('Contact');
Database.executeBatch(batch);
Test.stopTest();
List<ContentWorkspace> libraries = [
SELECT Id, Name
FROM ContentWorkspace
WHERE Name = 'ApexResults'
LIMIT 1
];
System.assert(!libraries.isEmpty(), 'library not found');
List<ContentVersion> documents = [SELECT Title FROM ContentVersion WHERE Title LIKE '%Contact%'];
System.assertEquals(2, documents.size(), 'csv not found');
}
}
実行方法
保存先の作成
ファイルから新規ライブラリの作成をしましょう。
ファイルのタブが出ていない場合は、公式のヘルプにもあるように
プロファイルで表示や、アプリケーションのタブに追加しましょう。
- 新規ライブラリの作成
作成する名前は以下のコードのName
と同じ名前で作成してください
※なんでもいいです。
List<ContentWorkspace> libraries = [
SELECT Id, Name
FROM ContentWorkspace
WHERE Name = 'ApexResults'
LIMIT 1
];
作成が完了したら、SOQLで目的のフォルダができているか確認しましょう。
- 設定>開発者コンソールから確認
QueryEditorの欄に以下のSOQLを添付し、Executeボタンで実行して結果が返ってこればOKです。
SELECT Id, Name
FROM ContentWorkspace
WHERE Name = 'ApexResults'
LIMIT 1
コードの実行
以下のコードを開発者コンソールの
- 実行匿名ウィンドウ
- VSCode でApexの匿名実行
のどちらかで実行
// オブジェクト名に取得したいオブジェクトのAPI参照名を入れてください
// 例) Account
CsvBatch batch = new CsvBatch('オブジェクト名');
Database.executeBatch(batch);
実行結果
実行すると作成したライブラリにファイルが、二つ生成されていますね。
それぞれのファイルの内容がどんなものか簡単に紹介します。
保存されるCSVの例
Account で実行した場合
SELECT結果のCSV
※保存されているデータはSalesforceのハンズオン組織のものです。
指定したオブジェクトの項目名をヘッダーにした、
全データのCSVが保存されます。
- Account.csv
Id | IsDeleted | MasterRecordId | Name | Type | ... |
---|---|---|---|---|---|
0015i00000KcklhAAB | false | Sample Account for Entitlements | ... | ||
︙ | ︙ | ︙ | ︙ | ︙ | ︙ |
オブジェクトの項目情報のCSV
指定したオブジェクトの項目名のAPI参照名、ラベルの情報が入ったCSVが出力されます。
- Account-Schema.csv
DeveloperName | Name |
---|---|
Id | 取引先 ID |
IsDeleted | 削除 |
MasterRecordId | マスタレコード ID |
Name | 取引先名 |
Type | 取引先 種別 |
︙ | ︙ |
コードの解説
先に実行方法と、Salesforce上でのライブラリの作成の説明をしたのでここからはコードの簡単な流れの説明をしていきます。
指定したオブジェクトの項目のAPI参照名を取得
Schema.SObjectType
Schema.DescribeSObjectResultを利用してオブジェクトの定義情報を取得して
API参照名をキー、ラベルを値にしたMapを作成し返します。
private Map<String, String> getFields(String objectName) {
Map<String, String> fields = new Map<String, String>();
Schema.SObjectType objectType = Schema.getGlobalDescribe().get(objectName);
Schema.DescribeSObjectResult describeObject = objectType.getDescribe();
Map<String, Schema.SObjectField> schemaFields = describeObject.fields.getMap();
for (Schema.SObjectField schemaField : schemaFields.values()) {
fields.put(schemaField.getDescribe().getName(), schemaField.getDescribe().getLabel());
}
return fields;
}
取得したAPI参照名でSOQLを作成し実行する
オブジェクト名から取得したAPI参照名を
Id, Name
のようにしてSOQL文を作成しオブジェクトに定義されている全項目でSOQLを実行します。
public Database.QueryLocator start(Database.BatchableContext bc) {
fields = getFields(objectName);
String columns = String.join(new List<String>(fields.keySet()), ', ');
String query = 'SELECT ' + columns + ' FROM ' + objectName;
return Database.getQueryLocator(query);
}
検索した結果を処理、CSVのヘッダーと行を作成する
検索結果のレコードを行単位で、Listに追加
CSVのヘッダーとなる項目名等をListに追加しています。
public void execute(Database.BatchableContext bc, List<SObject> records) {
// 取得したレコードでCSVの行を作成
// 例) '0015i00000KcklhAAB',false,'Sample Account for Entitlements'
for (SObject record : records) {
List<String> row = new List<String>();
for (String filedName : fields.keySet()) {
if (record.get(filedName) != null) {
row.add(String.valueOf(record.get(filedName)).escapeCsv());
} else {
row.add('');
}
}
objectCsvRowValues.add(String.join(row, ','));
}
// 項目のAPI参照名、ラベルでCSVの行を作成
schemaCsvColumnHeader = String.join(
new List<String>{ 'DeveloperName', 'Name' },
', '
);
List<String> fieldNames = new List<String>();
for (String filedName : fields.keySet()) {
List<String> row = new List<String>();
row.add(filedName.escapeCsv());
row.add(fields.get(filedName).escapeCsv());
schemaCsvRowValues.add(String.join(row, ','));
fieldNames.add(filedName.escapeCsv());
}
// オブジェクトの項目名でCSVのヘッダーを作成
objectCsvColumnHeader = String.join(fieldNames, ', ');
}
CSVをライブラリに保存
作成した保存先ライブラリContentWorkspaceオブジェクトの情報を取得し、
作成したCSVに保存するデータを、Blob.valueOfで文字列からBlobに変換し、CSVとして
ContentVersionオブジェクトに新規データとして保存します。
public void finish(Database.BatchableContext bc) {
// 保存先ライブラリの情報取得
List<ContentWorkspace> libraries = [
SELECT Id, Name
FROM ContentWorkspace
WHERE Name = 'ApexResults'
LIMIT 1
];
if (libraries.isEmpty()) {
System.debug('保存先ライブラリーが見つかりません。');
} else {
List<ContentVersion> documents = new List<ContentVersion>();
// 項目のAPI参照名、ラベルのCSVを作成
String schemaCsvFile =
schemaCsvColumnHeader +
'\n' +
String.join(schemaCsvRowValues, '\n');
ContentVersion schemaDoc = new ContentVersion(
Title = objectName + '-Schema',
PathOnClient = objectName + '-Schema' + '.csv',
VersionData = Blob.valueOf(schemaCsvFile),
FirstPublishLocationId = libraries[0].Id
);
documents.add(schemaDoc);
// レコード情報をCSVに
String objectCsvFile =
objectCsvColumnHeader +
'\n' +
String.join(objectCsvRowValues, '\n');
ContentVersion objectDoc = new ContentVersion(
Title = objectName,
PathOnClient = objectName + '.csv',
VersionData = Blob.valueOf(objectCsvFile),
FirstPublishLocationId = libraries[0].Id
);
documents.add(objectDoc);
// ContentVersionにCSVのデータを保存
insert documents;
}
}
最後に
SOQLでSQLの*のように全項目検索しようと思うとFIELDS関数を利用するのですが、LIMIT 200制限や対応していないオブジェクトも存在していたりするので、オブジェクトで定義されてる項目から動的にSOQLを作成して実行しよう!という感じでApexを書いてみました。
ETL等でBigQueryにガッツリSalesforceの分析基盤を作成する前にCSVで出力して
BigQueryで読み込んでデータを見てみる!とか全項目である程度のデータを手元で見たいとかでSOQL書かなくてもCSVにできるので便利そうな感じします。
Apexに関して、まだまだガバナ制限を考慮して作成できてない部分ありますので是非アドバイス等あればコメントで教えてくださると助かります。
参考にさせていただいた記事