crmprogrammer38の日記

主にDWHやSalesforceのプログラムメモです。

【Java】Interfaceやabstract classが何となく理解できた時の思い出

最初のプログラム言語はJavaでした。インスタンスやクラス、インターフェースや抽象クラス、キャストなど色々な横文字が大量に出てきて何が何やらわからず、苦労した思い出があります。(当時、WebObjectsというAppleのWebフレームワークを利用していました)

 

ある時なんとなくJavaが理解できた時の思い出です。

 

インスタンスとは

クラスをnew するとインスタンス(実体)になります。インスタンスは常にどのクラスのインスタンスなのかを持っています。例としたBigDecimalを使います。

f:id:crmprogrammer38:20170818092626p:plain

コードで書くと次になります。

    new BigDecimal(100);
    new BigDecimal(200);

 変数とは

Javaでは、継承:extends と実装: implements の仕組みがあります。
その仕組みの中で"インスタンスの参照"を格納できる変数の型に制限があります。
その制限は、インスタンスのクラスの定義で、自身のクラス以上が継承したクラスや、自身のクラス以上が実装したインタフェースのみインスタンスの参照を保持できるというものです。

BigDecimalが継承したクラスと実装したインタフェースは次の通りで、次のクラスとインタフェースがBigDecimalインスタンスを格納できる。
f:id:crmprogrammer38:20170818093725p:plain

クラスから作成したインスタンスを格納できる変数の関係は次の通りです。

f:id:crmprogrammer38:20170818094647p:plain
コードにすると次になります。

    BigDecimal decimal100 = new BigDecimal(100);
    Object obj100 = decimal100;
    Number num100 = decimal100;
    Serializable sel100 = decimal100;
    Comparable com100 = decimal100;
    
    BigDecimal decimal200 = new BigDecimal(200);
    Object obj200 = decimal200;
    Number num200 = decimal200;
    Serializable sel200 = decimal200;
    Comparable com200 = decimal200;

サンプルとしてNumber型の変数num100を例にするとnum100に格納されているのは、BigDecimalクラスのインスタンスなので、キャストをすればBigDecimal型に格納することもできます。

    BigDecimal decimal100_c = (BigDecimal)num100;

 

抽象クラス

いまいちインターフェースとの使い分けがわからなかったのですが、抽象クラスは実装メソッドが持てて、そのメソッドの中で抽象メソッドも使えるというのが理解できてなんとなく使い方がわかりました。


public abstract class AbstractSample {
  
  protected void start(){
  }
  
//抽象メソッド
  abstract protected void execute();
  
  protected void end(){    
  }
  
//メソッド内で、抽象メソッドを使っておく。
  public void process(){
    start();
    execute();
    end();
  }
}

共通の部分はabstarct クラス側で実装しておいて、個別の処理はextendしたクラス側で実装を強制させることができます。似たような処理をクラスを分けて実現する場合、きれいに書けると思います。(現実は、1つのメソッドの中で分岐で実現することがほとんどだと思いますが。。)

 

終わりに

WebObjectsもそうですが、Javaのクライアント側のSwingなどは、抽象クラス、インタフェースなどを書いて覚えるにはちょうど良いと思います。どちらもメジャーではないんですよね。

【Salesforce】重複ルールで作成・編集を許可している時の、標準画面以外からのデータの登録について

Salesforceで、重複ルールという機能があります。(前はなかったのですが、とても欲しかった機能でした。実装されてちょっと嬉しかったです)

 

重複ルールの設定次第では、重複レコードが他にあった際に警告は出るが保存できるということができます。

この設定にした際の、API実装や、データローダやApexで気をつけないといけない点になります。

 

API実装で、重複レコードの場合に保存する方法

SOAPHeaderに、DuplicateRuleHeaderを指定する。DuplicateRuleHeaderではallowSaveをtrueにする。

  PartnerConnection pcon = Connector.newConnection(connectorConfig);
  pcon.setDuplicateRuleHeader(true, false, true);

 この指定をすることで、エラーとならずに保存ができます。
ETLツールでもSOAPHeaderの指定が可能であればこの設定ができると思います。
指定しない場合allowSaveの初期値はfalseなので、常にエラーとなります。

 

そして、APIには、事前に重複ルールに該当するか調べるメソッドPartnerConnection.findDuplicatesが用意されています。PartnerConnection.findDuplicatesでは、データ登録前に重複データあるか、ある場合の重複する他のデータを取得することができます。

  SObject sobj = new SObject();
  sobj.setType("Sample__c");
  sobj.setField("KeyField__c", "Value01");
  
  FindDuplicatesResult[] result = pcon.findDuplicates(new SObject[]{sobj});

 

データローダでは、重複レコードの場合は無条件で登録できない

データローダでは、DuplicateRuleHeaderが指定できない(APIVer40時点)ため、重複ルールで該当すると無条件でエラーとなります。

データローダではエラーではなく登録させたい場合は、重複ルールの条件に工夫が必要となります。

例えば、データローダを利用するユーザのプロファイルやロールを重複ルールの条件に追加することで、そのユーザでは登録ができるようにする対応を行います。

ApexではDMLOptionで指定する

Apexで重複ルールに該当しても、そのまま登録するにはDMLOptonの指定を行います。

Database.DMLOptions dml = new Database.DMLOptions();
dml.DuplicateRuleHeader.allowSave = true;
dml.DuplicateRuleHeader.runAsCurrentUser = true;

Account duplicateAccount = new Account(Name='duplicate');

Database.SaveResult sr = Database.insert(duplicateAccount, dml);

 

最後に

重複ルールを使う場合、標準画面のチェックが便利になった分、それ以外の考慮が必要になります。データ連携があるシステムでは、データ連携への影響を検討が必要ですし、そのオブジェクトを利用するVisualforceページを作成する場合は、そのVisualforceページの動きの確認が必要です。

【Excel】セルの表をtableタグで出力するマクロをバージョンアップしました

以前、Excelの表をhtmlのtableタグに変換するマクロを作成しました。

 

crmprogrammer38.hatenablog.com

 

使っていくうちにやっぱりこうしたほうがいいかなとか考えていき、次のように改良しました。

  1. マクロの中でセルの値をValueで取得していた箇所をTextで取得するように修正
  2. 文字列の位置(上揃え、下揃え、中央揃え、右揃え)をタグに反映する

 

1は日付のフォーマットや数値の区切りはそのままhtmlになってくれた方が便利だなと思い対応しました。

2は文字の揃えをvalignやalignに反映されたほうが楽だなと思い対応しました。(あくまでExcel上で指定した設定が反映されます。例えば数値は指定しなくても表示上は右揃えですが、Excel上で設定していなければalignには反映されません)

 

ダウンロードはこちらからになります。

 

最後に

同じようなマクロやツールは、他に探せば他に色々なあったのですが、結局自分で使いやすいようにいじくれるというのが便利で、結局これを使ってます。
もっと、使いやすいツールがあると思うのですが、自分で創意工夫ができる余白がある方が楽しいんですよね。

【Salesforce】APIの「renderEmailTemplate」「renderStoredEmailTemplate」を使ってみた

前回SalesforceAPIについて書きました。使ったことのないメソッドが増えているなーと思い、まず「renderEmailTemplate」「renderStoredEmailTemplate」を使ってみました。

「renderEmailTemplate」

	String endpoint = "https://login.salesforce.com/services/Soap/u/40.0";
	ConnectorConfig pconfig = new ConnectorConfig();
	pconfig.setAuthEndpoint(endpoint);
	pconfig.setServiceEndpoint(endpoint);
	pconfig.setUsername("sampleuser@sample.user");
	pconfig.setPassword("samplepassword");
	PartnerConnection pConnection = new PartnerConnection(pconfig);

	RenderEmailTemplateRequest req = new RenderEmailTemplateRequest();
	//ここでテンプレート文字列を指定する。
	req.setTemplateBodies(new String[] {"aaaaa {!Sample__c.Id} bbbb {!Receiving_User.Name}"});
	//レコードのID
	req.setWhatId("XXXXXXXXXXXXXXX");
	//受信ユーザ項目に指定するユーザID
	req.setWhoId("005XXXXXXXXXXXX");
	
	RenderEmailTemplateResult[] result = pConnection.renderEmailTemplate(new RenderEmailTemplateRequest[]{req});
	
	System.out.println(result[0].getBodyResults()[0].getMergedBody());

RenderEmailTemplateRequest.setTemplateBodiesは引数は文字列の配列で、件名や本文をまとめて指定できるようになっています。

「renderStoredEmailTemplate」

	String endpoint = "https://login.salesforce.com/services/Soap/u/40.0";
	ConnectorConfig pconfig = new ConnectorConfig();
	pconfig.setAuthEndpoint(endpoint);
	pconfig.setServiceEndpoint(endpoint);
	pconfig.setUsername("sampleuser@sample.user");
	pconfig.setPassword("samplepassword");
	PartnerConnection pConnection = new PartnerConnection(pconfig);

	RenderStoredEmailTemplateRequest req = new RenderStoredEmailTemplateRequest();
	//ここでメールテンプレートのIDを指定する。
	//メールテンプレート(EmailTemplate)オブジェクトからIDを取得する処理が必要	
	req.setTemplateId("00X0H000001V8keUAC");
	//レコードのID
	req.setWhatId("XXXXXXXXXXXXXXX");
	//受信ユーザ項目に指定するユーザID
	req.setWhoId("005XXXXXXXXXXXX");
	//この指定は必須
	req.setAttachmentRetrievalOption(AttachmentRetrievalOption.MetadataOnly); 
	
	RenderStoredEmailTemplateResult result = pConnection.renderStoredEmailTemplate(req);
	System.out.println( result.getRenderedEmail().getPlainTextBody() );
	System.out.println( result.getRenderedEmail().getSubject() );

メールテンプレート(EmailTemplate)をSOQLで検索し該当のテンプレートのIDを取得する処理が必要になります。(上の処理では書いていませんが。。)

終わりに

APIだけでなく、ApexClassでも同様の処理が用意されていて、「Messaging.renderEmailTemplate」「Messaging.renderStoredEmailTemplate」が対応します。

メールで送る文字列そのものが必要な時にはとても重宝すると思います。
ちなみに、メールテンプレートは、カスタム表示ラベルを使うことができますが、いまだに差し込み項目の種類で選べないですね。。

【Salesforce】APIで用意されている機能について

SalesforceにはいくつかのAPIが用意されています。こういうことをやりたいときにこれを使えばいいというのがわかるようにまとめてみました。Rest API、Bulk APIは外していて、SOAP APIに限定しました。(APIVer40の時に書いています)

 

メソッド 処理内容 どのAPIの機能か
Enterprise/Partner
Metadata
Apex
Tooling
convertLead リードを取引先にする。      
changeOwnPassword 自身のパスワードを変更する。      
create レコードを作成する。      
delete レコードを削除する。      
describeGlobal 主に組織内のSobject情報をリストで取得する。      
describeSObject   Sobject情報を取得する。      
describeXXX その他の情報XXXを取得できる。      
emptyRecycleBin   ゴミ箱を空にする。      
executeListView   リストビューを実行し、結果を取得する。      
findDuplicates 登録する前に重複ルールに該当結果を取得する。      
performQuickActions   アクションを実行する。      
login ログインする。(セッションIDと、インスタンスURLを取得する)    
logout ログアウトする。(ログイン時のセッションIDを終了する。)    
merge    レコードをマージする。(リード、取引先、取引先責任者に限定された機能)      
process   承認プロセスの操作をする。      
query    SOQLを実行し、レコードを取得する。      
queryAll   SOQLを実行し、削除済みレコードを含めてレコードを取得する。      
queryMore    query/queryAllの次の結果を取得する。(検索結果の件数が多い場合、何度かにわけて結果を取得する)      
renderEmailTemplate   メールテンプレート文字列とレコードの値をマージする。      
renderStoredEmailTemplate    定義したメールテンプレートとレコードの値をマージする。      
resetPassword    指定したユーザのパスワードをリセットし、リセット後のパスワードを取得する。(通知メールを送るわけではない)      
setPassword    指定したユーザに指定したパスワードをセットする(ログイン後、パスワードの変更画面が表示される。)      
retrieve   SalesforceIdを指定してレコードを取得する。      
search グローバル検索のようにレコードを検索し、結果を取得する。      
sendEmail    指定した宛先、件名、本文、添付ファイルでメールを送信する。      
sendEmailMessage   Sobject「メールメッセージ(EmailMessage)」のドラフトメールを送信する。      
undelete   ゴミ箱から復元する。      
update レコードを更新する。      
upsert レコードをアップサートする。      
getServerTimestamp Salesforceからサーバ時刻を取得する。    
getUpdated 指定されたオブジェクトに対して指定された期間内に更新されたレコードを取得する。      
getDeleted 指定されたオブジェクトに対して指定された期間内に削除されたレコードを取得する。      
getUserInfo ログインしたユーザ情報を取得する。    
invalidateSessions 指定されたセッションIDを終了する。    
deploy メタデータをデプロイする。      
checkDeployStatus デプロイの状況を取得する。(deployは非同期のため定期的に状況をチェックする。)      
cancelDeploy デプロイをキャンセルする。      
deployRecentValidation クイックリリースする。      
retrieve メタデータを取得する。      
checkRetrieveStatus メタデータの取得状況を取得する。(メタデータの取得は非同期のため定期的に状況をチェックする。)      
describeMetadata メタデータタイプの一覧を取得する。      
listMetadata 指定したメタデータタイプのコンポーネント一覧を取得する。      
createMetadata メタデータコンポーネントを作成する。      
upsertMetadata メタデータコンポーネントをアップサートする。      
updateMetadata メタデータコンポーネントを更新する。      
deleteMetadata メタデータコンポーネントを削除する。      
readMetadata メタデータコンポーネントを取得する。      
renameMetadata メタデータコンポーネントをリネームする。      
compileClasses クラスを作成する。(開発環境用)      
compileTriggers トリガを作成する。(開発環境用)      
compileAndTest クラスを作成しテストする。(本番環境用)      
runTests テストを実行する。    
executeAnonymous AnonymousApexを実行する。    
create Tooling API objectsを作成する。      
update Tooling API objectsを更新する。      
upsert Tooling API objectsをアップサートする。      
delete Tooling API objectsを削除する。      
search グローバル検索のようにTooling API objectsを検索し、取得する。      
query SOQLのようにTooling Api Objectsを検索し、取得する。      
queryAll 削除されたTooling API objectsを含めてSOQLのようにTooling API objectsを検索し、取得する。      
queryMore query/queryAllの次の結果を取得する。(検索結果の件数が多い場合、何度かにわけて結果を取得する)      
describeGlobal Tooling API objectsの一覧を取得する。      
describeSObject 指定したTooling API objectsの詳細を取得する。      
describeXXX その他のTooling API objectsの情報を取得する。      
retrieve Idを指定してTooling API objectsを取得する。      
getUpdated 指定された期間内に更新されたTooling API objectsを取得する。      
getDeleted 指定された期間内に削除されたTooling API objectsを取得する。      
runTestsAsynchronous 非同期でテストを実行する。      

 ※APIメソッドはすべてではありません。

 

まとめていく中で、Tooling APIの使い道がピンときませんでした。開発者コンソールの一部では使っていると思いますが、自分の中ではこういう所で使えそうというイメージが持てませんでした。

Enterprise/Partner APIと同じ名前のメソッドが、Tooling APIで定義されていますが、取得するのがTooling API Objectsというメタデータ寄りの定義情報になります。

メタデータの作成や取得をするならMetadata APIの方を使うことになりますし、Tooling APIだけが持っているメソッドはほぼありません。

Tooling API Objectsがいまいち理解できていないのだと思いますが、もし便利な使い方があれば教えてください。

 

あと、APIを使う上での注意事項は次になります。

  • 基本logoutは使いません。データ連携などで、同じユーザ、同じタイミングで複数の処理が動作する可能性があります。1つの処理でlogoutすると、同時に動いているほかの処理でセッションIDが無効のエラーとなってしまいます。
  • APIのコール数の上限や、送信メール数の制限を超えないようにします。 
  • sendEmailでメールを送信する時は、エラーとなる宛先があるなどSalesforceに負荷がかかると処理が中断される場合があります。
  • レコードの作成、更新などでもSalesforceに負荷がかかると処理が中断される場合があります。

 

【SQL】Window関数を理解できた時の思い出2

前の続きです。

crmprogrammer38.hatenablog.com

 

Window関数では、Windowの中で並び替えをした順番に各行で計算する仕組みがあります。例としては累計の計算になります。(BIツールで、ランニングサムの名称がついていたりします)

以下の年別累計の項目は、年毎のWindowの中で年月の古い順に出費を足した値となります。

年月 出費 年別累計
2016 201611 27,000 27,000
2016 201612 24,000 51,000
2017 201701 18,000 18,000
2017 201702 3,000 21,000
2017 201703 15,000 36,000
2017 201704 27,000 63,000

これを出力するSQLは次になります。(rows betweenを省略しないで書いています)

select
  年 
 ,年月 
 ,出費 
 ,sum(出費) over(partition by 年 order by 年月 
                 rows between unbounded preceding and current row ) as 年別累計
from データ;

このSQLでrows between~の記載がありますが、ここで行を計算する際に、Windowのどこからどこまでを計算対象とするかの指定をしています。

英語の通りなのですが、Window内の、先頭行(unbounded preceding)から現在行(current row)までをsumするという指定になります。

※補足ですが、sumやcountなどのWindow関数でorder byを指定した場合は、"rows between unbounded preceding and current row"がデフォルトなので省略ができます。

 

rows betweenで次を指定できます。

unbounded preceding 先頭行
N preceding  N行前の行
current row 現在行
N following N行後の行
unbounded following 最終行

 

上の指定を組み合わせると以下のような結果がとれます。

  • 1行前の出費は、1段下がったように値が表示されます。
  • 1行後の出費は、1段上がったように値が表示されます。
  • 現在の行数は、年単位に行毎に数が増えています(row_numberと同じ結果です)
  • 残り行数は、そのWindowで現在行を含めずに何行残っているが表示されます。
年月 出費 1行前の出費 1行後の出費 現在の行数 残り行数
2016 201611 27,000   24000 1 1
2016 201612 24,000 27,000   2 0
2017 201701 18,000   3000 1 3
2017 201702 3,000 18,000 15000 2 2
2017 201703 15,000 3,000 27000 3 1
2017 201704 27,000 15,000   4 0

 これを出力するSQLは次になります。

select
  年 
 ,年月 
 ,出費 
 ,sum(出費) over(partition by 年 order by 年月 
                 rows between 1 preceding and 1 preceding ) as "1行前の出費"
 ,sum(出費) over(partition by 年 order by 年月 
                 rows between 1 following and 1 following ) as "1行後の出費"
 ,count(*)  over(partition by 年 order by 年月 
                 rows between unbounded preceding and current row) as "現在の行数"
 ,count(*)  over(partition by 年 order by 年月 
                 rows between 1 following and unbounded following ) as "残り行数"
from データ;

rows between で範囲をどこからどこまでにするかで欲しい値が取得できます。

 

最後に

rows between以外にもrange betweenというのもありますが触れないでおこうと思います。

 

Window関数があると便利だなと思っているのは次です。

キー 開始日 その他属性・・
KEY001 2016-01-01 ・・
KEY001 2016-08-15 ・・
KEY001 2017-03-01 ・・
KEY002 2016-04-10 ・・
KEY002 2017-02-01 ・・

 上記は、いわゆる履歴管理されているマスタですが、開始日だけ持っています。

結合条件で使うため終了日を計算する場合があります。終了日は同じキーの次の開始日の1日前、最終行の場合は固定で2100-12-31とします。

その時にWindow関数を使うと次のようなデータが作れます。

キー 開始日 終了日 その他属性・・
KEY001 2016-01-01 2016-08-14 ・・
KEY001 2016-08-15 2017-02-28 ・・
KEY001 2017-03-01 2100-12-31 ・・
KEY002 2016-04-10 2017-01-31 ・・
KEY002 2017-02-01 2100-12-31 ・・

この結果となるSQLは次の通りです。(Oracle用のため日付の計算はDB毎で変更が必要です。)

Select
   キー
  ,開始日
  ,coalesce( 
     max( 開始日 ) over( partition by キー order by 開始日 
                         rows between 1 following and 1 following ) -1  
  , to_date('21001231','YYYYMMDD') ) as 終了日
  ,その他属性
From
  履歴を持つマスタ

DWHなどを作っていると使う時も多いかなと思います。

【SQL】Window関数を理解できた時の思い出

はじめてのデータベースは、Oracle8でした。

その時はWindow関数自体なく、検索条件と結合条件を入り混じるSQLを書いていました。

 

その後Oracle9になって、SQLも変化していましたが、Window関数とは距離を置いていました。あまり使う必要性を感じなかったのもありますが、よく理解できなかったというのが正直なところです。

 

理解できたときの思い出を書こうと思います。

 

まず、Windowを理解する

最初わけがわからなかったのですが、Windowや、関数の動く順番をイメージできたら理解できました。

Window関数は、検索結果を取得後、その結果に対して動く関数

このイメージを持ててからは早かったです。

例えば次のデータがあります。

年月 出費
2016 201611 27,000
2016 201612 24,000
2017 201701 18,000
2017 201702 3,000
2017 201703 15,000
2017 201704 27,000

 このデータの検索結果全体に対してWindow関数を指定すると次のようになります。Window内で集計した結果が各行にあります。

年月 出費 合計件数 合計出費
2016 201610 27,000 6 114,000
2016 201611 24,000 6 114,000
2016 201612 18,000 6 114,000
2017 201701 3,000 6 114,000
2017 201702 15,000 6 114,000
2017 201703 27,000 6 114,000

この時のSQLは次のようになります。

select
  年
 ,年月
 ,出費
 ,count(*)  over() as 合計件数
 ,sum(出費) over() as 合計出費
from
  データ

over() を指定すると検索結果全体がWindowになります。

 

そして、年単位でWindowを指定することもでき、その場合次の結果となります。

年月 出費 合計件数 合計出費 年別_合計件数 年別_合計出費
2016 201611 27,000 6 114000 2 51,000
2016 201612 24,000 6 114000 2 51,000
2017 201701 18,000 6 114000 4 63,000
2017 201702 3,000 6 114000 4 63,000
2017 201703 15,000 6 114000 4 63,000
2017 201704 27,000 6 114000 4 63,000

 この時のSQLは次の通りです。

select
  年
 ,年月
 ,出費
 ,count(*)  over() as 合計件数
 ,sum(出費) over() as 合計出費
 ,count(*)  over(partition by 年) as 年別_合計件数
 ,sum(出費) over(partition by 年) as 年別_合計出費
from
  データ

 over(partition by 年) を指定することで年毎でWindowになります。

 

Windowの考え方は基本は上記の通りです。

rows between ( preceding や following、current row )の指定はあるのですが、上記を理解できた後は早かったように思います。別の機会に残りは書いてみたいと思います。