LoginSignup
8
4

More than 1 year has passed since last update.

Salesforce ApexでSOQLの結果をCSVとして保存する

Last updated at Posted at 2022-09-19

はじめに

今回は、Salesforceのオブジェクトの項目・データをCSVでサクッと取得するためにApexを書きます。

自分は、普段からSalesforceからレコードや、スキーマ情報をApexで取らずに

  • API
  • CLI
    • SalesforceDX

等でサクッととっているのですが、今回学習のためにApexでやってみました。

今回動かすApexは、
とりあえず全項目を検索し、CSVに出力する作りにしているので
大量データのバックアップ等には向いていません

この記事でやること

  • 以下のApexを作成
    • オブジェクト名を指定することで全部の項目をSELECTする
    • CSVとしてSalesforceのファイル>ライブラリに保存する
      • 保存するもの
        • 検索結果のレコード
        • 検索した項目のAPI参照名とラベルのセット
  • Apex実行後に保存されるファイル>ライブラリの作成

実行環境

  • trailhead ハンズオン組織
  • 利用するデータ ハンズオン組織のサンプルデータ

書いたコード

今回書いたコードはこちらです。

CsvBatch.cls
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の中身までは検証していませんが、ファイルが生成されたかどうかだけチェックしています。

CsvBatch_Test.cls
@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');
  }
}

実行方法

保存先の作成

ファイルから新規ライブラリの作成をしましょう。

ファイルのタブが出ていない場合は、公式のヘルプにもあるように
プロファイルで表示や、アプリケーションのタブに追加しましょう。

  • 新規ライブラリの作成

スクリーンショット 2022-09-20 3.42.08.png

作成する名前は以下のコードのNameと同じ名前で作成してください
※なんでもいいです。

    List<ContentWorkspace> libraries = [
      SELECT Id, Name
      FROM ContentWorkspace
      WHERE Name = 'ApexResults'
      LIMIT 1
    ];

スクリーンショット 2022-09-20 3.42.50.png

作成が完了したら、SOQLで目的のフォルダができているか確認しましょう。

  • 設定>開発者コンソールから確認

スクリーンショット 2022-09-20 3.52.51.png

QueryEditorの欄に以下のSOQLを添付し、Executeボタンで実行して結果が返ってこればOKです。

SELECT Id, Name
      FROM ContentWorkspace
      WHERE Name = 'ApexResults'
      LIMIT 1

スクリーンショット 2022-09-20 3.57.53.png

コードの実行

以下のコードを開発者コンソールの

のどちらかで実行

実行する際のコード
// オブジェクト名に取得したいオブジェクトのAPI参照名を入れてください
// 例) Account
CsvBatch batch = new CsvBatch('オブジェクト名');
Database.executeBatch(batch);

スクリーンショット 2022-09-20 4.18.37.png

実行結果

実行すると作成したライブラリにファイルが、二つ生成されていますね。

それぞれのファイルの内容がどんなものか簡単に紹介します。

スクリーンショット 2022-09-20 4.19.42.png

保存される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に関して、まだまだガバナ制限を考慮して作成できてない部分ありますので是非アドバイス等あればコメントで教えてくださると助かります。

参考にさせていただいた記事

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4