プログラマ38の日記

主にプログラムメモです。

Salesforce: データローダのコマンドライン操作のメモ

ETLツールがない場合など、Salesforceとのバッチ連携はCSVファイルとデータローダのコマンドラインの組み合わせで行うことになると思います。(APIで開発は意外と手間がかかります)

データローダをコマンドラインで使った際のメモを書いておこうと思います。

 

日付型の形式

yyyy-MM-dd 形式(例えば、2017年7月14日なら、2017-07-14)とし、sfdc.timezoneをUTCにする。他のやり方(JSTにして、フォーマットに時間まで含める)もありますが、こちらの方が違和感ないと思います。

日付/時間型の形式

yyyy-MM-dd'T'HH:mm:ss'.000+0900'形式(例えば、日本時間2017年7月14日8時55分13秒なら、2017-07-14T08:55:13.000+0900)

日本時間でデータを入れるケースが多いと思うのでこうしました。GMT形式で9時間マイナスする処理が不要になります。

※補足ですが、SOQLで日付/時間項目への検索条件もこの形式で可能です。

マッピングファイル(sdlファイル)の書き方のポイント

  1. Salesforceの項目名は、大文字小文字を区別する
  2. 参照関係項目に、参照先の外部IDを使用する時は「CSV項目=参照先名:参照先の外部ID項目名」の形式
  3. 固定値をセットしたい場合は、ダブルクオートで区切ってセットする「"固定値"=項目名」

2の外部IDを使用する場合ですが、複数の親オブジェクトを指定できる項目へは使えません。また、外部IDしか使えないのが辛いところです。

APIで開発する場合は、複数の親オブジェクトを指定する項目にも外部IDを指定することができます。さらに外部IDだけでなく、IdLookupが可能な項目が全て使えますので、ユーザオブジェクトの場合、ユーザオブジェクトの外部IDだけでなく、ユーザ名(Username)、メール(Email)、SAML 統合 ID(FederationIdentifier)も使うことができます。ETLツールのアダプタによっては対応してるものもあると思います。)

サンプルで用意されているprocess-conf.xmlxml宣言が省略されている

なぜかサンプルでは、xml宣言が省略されているのでutf-8で保存しないとエラーになります。マルチバイトを使わなければ問題はないですが、マルチバイトを使った時にうっかりshift_jis(CP932の方)で保存すると実行時にエラーになります。

バッチ処理の中でprocess-conf.xmlを出力したいことが多いと思いますし、その際にutf-8文字コードでの保存が面倒なので、きちんとxml宣言を追加してshift_jisで保存するようにしておけば楽だと思います。

sampleのprocess-conf.xml

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="accountMasterProcess"
          class="com.salesforce.dataloader.process.ProcessRunner"
          singleton="false">
        <description>AccountMaster job gets the Customer record updates from ERP (Oracle financials) and uploads them to salesforce using 'upsert'.</description>
        <property name="name" value="accountMasterProcess"/>
 ・・・・

 文字コードを指定した後のprocess-conf.xml

<?xml version="1.0" encoding="Windows-31J" standalone="no" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="accountMasterProcess"
          class="com.salesforce.dataloader.process.ProcessRunner"
          singleton="false">
        <description>AccountMaster job gets the Customer record updates from ERP (Oracle financials) and uploads them to salesforce using 'upsert'.</description>
        <property name="name" value="accountMasterProcess"/>
 ・・・・

 

ログインユーザ、パスワードではなく、インスタンスURLとセッションIDで利用する

process-conf.xmlに、"sfdc.oauth.accesstoken"の指定をすれば、"sfdc.username"と"sfdc.password"の指定は不要です。"sfdc.oauth.accesstoken"の指定をする場合、"sfdc.endpoint"は、インスタンスURLで指定することになります。

 

"sfdc.oauth.accesstoken"で指定する場合、process-conf.xmlは次のようになります。

<?xml version="1.0" encoding="Windows-31J" standalone="no" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="export" class="com.salesforce.dataloader.process.ProcessRunner" singleton="false">
    <description>export</description>
    <property name="name" value="export"/>
    <property name="configOverrideMap">
      <map>
        <entry key="process.operation"                 value="extract"/>
        <entry key="process.enableExtractStatusOutput" value="false"/>

        <entry key="sfdc.endpoint"                     value="instanceURL"/>
        <entry key="sfdc.oauth.accesstoken"            value="sessionId"/>
・・・・・・・・・

Salesforce画面からインスタンスURLとセッションIDをパラメータとして渡すことができれば、データローダをおいてある外部のサーバから、画面を操作した人としてレコードの作成・更新が可能になります。

また、いちいちログインしたくない場合などにも使えます。

 

 

所感

process-conf.xmlのテンプレートをextract、insert、upsert、deleteで用意しておいて、処理別に値を書き換えて使うようにすると使いやすいなと感じています。

また、個人的には、値の書き換えは環境変数を展開するようにしています。

 

環境変数の展開前(extractの場合)

<?xml version="1.0" encoding="Windows-31J" standalone="no" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="export" class="com.salesforce.dataloader.process.ProcessRunner" singleton="false">
    <description>export</description>
    <property name="name" value="export"/>
    <property name="configOverrideMap">
      <map>
        <entry key="process.operation"                 value="extract"/>
        <entry key="process.enableExtractStatusOutput" value="false"/>

        <entry key="sfdc.endpoint"                     value="%sfdc.endpoint%"/>
        <entry key="sfdc.username"                     value="%sfdc.username%"/>
        <entry key="sfdc.password"                     value="%sfdc.password%"/>
        <entry key="sfdc.timeoutSecs"                  value="600"/>
        <entry key="sfdc.loadBatchSize"                value="200"/>
        <entry key="sfdc.timezone"                     value="UTC"/>
        <entry key="sfdc.insertNulls"                  value="false"/>
        <entry key="sfdc.extractionRequestSize"        value="500"/>


        <entry key="sfdc.entity"                       value="%sfdc.entity%"/>
        <entry key="sfdc.extractionSOQL"               value="%sfdc.extractionSOQL%"/>

        <entry key="dataAccess.name"                   value="%daaAccess.name%"/>
        <entry key="dataAccess.writeUTF8"              value="false"/>
        <entry key="dataAccess.type"                   value="csvWrite"/>

      </map>
    </property>
  </bean>
</beans>

としておいて、環境変数に上記の変数を用意しておきます。

そして、VBScriptのWScript.Shellオブジェクトの、ExpandEnvironmentStrings関数で環境変数を展開するのが便利です。

 

次がExpandEnvironmentStrings関数を利用して環境変数を展開するプログラムサンプルです。

仕様:引数にShift_JISのファイル名を指定し、ファイルのテキストの環境変数を展開して標準出力に出力する

Option Explicit

Dim objFSO      ' FileSystemObject
Dim objFile     ' ファイル読み込み用

Dim file        'ファイル名
Dim filetxt     'ファイル文字列

file=WScript.Arguments(0)
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
If Err.Number = 0 Then
    Set objFile = objFSO.OpenTextFile(file)
    If Err.Number = 0 Then
        filetxt = objFile.ReadAll
        objFile.Close
    Else
        WScript.Echo "ファイルオープンエラー: " & Err.Description
    End If
Else
    WScript.Echo "エラー: " & Err.Description
End If

Set objFile = Nothing
Set objFSO = Nothing

'変数
Dim objShell
Dim tmp
 
'シェルオブジェクト作成
Set objShell = CreateObject("WScript.Shell")
 
'環境変数展開
tmp = objShell.ExpandEnvironmentStrings(filetxt)
 
'環境変数表示
WScript.Echo tmp
 
'シェルオブジェクト破棄
Set objShell = Nothing