プログラマ38の日記

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

Salesforce: Apex開発で最初の頃にやってしまった間違い

Java開発の後、Salesforceの開発を始めたのですが、当初色々間違いや勘違いがありました。思い出すことがあったので書いてみます。

 

booleanの変数の扱い

Javaですとboolean変数は true/false のプリミティブ型で、Booleanと書くとオブジェクトとなりますが、SalesforceではbooleanとBooleanの区別はなく、true/false/nullの3パターンとなります。

次のようにクラスのフィールドに定義した boolean flag; の初期値はnullです。

public class Sample {
  boolean flag;
}

Sobjectのチェックボックスの項目はnullを指定するとエラーなので上のflagの値ををそのままチェックボックスの項目にセットするとデータ登録・更新時にエラーになります。

 

ifの分岐でもflagがtrue以外の時の処理を書く際に、

if( flag == false ){
  ・・・・
}

と書くと、flagがnullの時に処理を通らずバグになります。

 

insert後のSobjectのIdは自動でセットされる

SobjectをinsertするとId項目は作成したレコードのIdが入ります。

Account newacc = new Account();
newacc.Name = 'Test1';
・・・
System.debug(newacc.Id);
insert newacc;
System.debug(newacc.Id);

上記のnewacc.Idはinsert前はnullですが、insert後は作成したレコードのSalesforceID
が入ります。

次のようにinsertした後、オブジェクトを再度検索していた所があり、他の箇所で同じタイミングで連続してデータを作成されてバグがでていました。

Account newacc = new Account();
newacc.Name = 'Test1';
・・・
insert newacc;
//なぜか作成日付が最後の行を再度検索
newacc = [select Id, ・・・ from Account Order by CreatedDate desc limit 1];

数式項目や、連番、監査項目などを使いたくて再度検索することはあると思いますが、その場合は次のようにIdを検索条件にいれないとバグになります。

newacc = [select Id, ・・・ from Account where ID = newacc.Id];

文字列が同じ値の判断

Javaの同じ文字列か判断する時は、equalsを使います。Salesforceでもequalsは使えるので同様に書くことはできます。ですが、 Salesforceでは == でも同じ文字列かの判断に使えるので、通常は == を使っていきます。 == を使った場合、両方の文字列がnull の場合trueとなります。ですが、あえてequalsを使い、両方の文字がnullの時にNullPointerExceptionが発生した時がありました。わざわざコード量増やしてバグを出さなくてもと思います。

String var1 = null;
String var2 = null;

//通常の書き方
if( var1 == var2 ){
・・
}

//どうしてもequalsを使いたい
if(  (var1 == null && var2 == null) || var1.equals( var2 ) ){
・・
}

 

最初は色々ありますよね。。

SQL: 縦横変換で気をつけていること

SQLで縦持ちから横持ちへ変換、横持ちから縦持ちの変換を行うことは多いと思います。

横持ちから縦持ちへの変換は、union all を使うことで実現ができ、union allはどのデータベースでも使えるので困ることは特にありません。

ですが、縦持ちから横持ちへの変換はデータベースでやり方を変える必要があり気をつけています。

 

[縦持ちのデータ]

グループ 担当
Aグループ 佐藤
Aグループ 山田
Aグループ 田中
Aグループ 鈴木
Bグループ 池田
Bグループ 田中
Bグループ 鈴木

[横持ちのデータ]

グループ 担当1 担当2 担当3 担当4 担当5
Aグループ 佐藤 山田 田中 鈴木  
Bグループ 池田 田中 鈴木    

上記のデータで縦持ちから横持ちへの変換を考えます。

上記の縦持ちのデータの担当者は実はあいうえお順で並んでいます。そして、縦持ちの時の並びでそのまま担当1、担当2…と横持ちに変換されています。

 

上の縦持ちから横持ちへの変換は次のようになります。

  1. 縦持ちのデータに、グループ毎に担当者順で1から連番を振る。
  2. 1で振られた連番を場合分けの条件に使い担当1~5の項目を用意した後、グループで集約する。


1の後のデータは次の通りです。

グループ 担当 連番
Aグループ 山田 1
Aグループ 田中 2
Aグループ 鈴木 3
Aグループ 佐藤 4
Bグループ 田中 1
Bグループ 鈴木 2
Bグループ 池田 3

 

そして2のSQLは次の通りとなります。※caseの部分は、データベース毎の関数に置き換えて使う事になります。(MS-ACCESSだとIIFを使います)

Select
グループ
,max( case when 連番=1 then 担当 end )
,max( case when 連番=2 then 担当 end )
,max( case when 連番=3 then 担当 end )
,max( case when 連番=4 then 担当 end )
,max( case when 連番=5 then 担当 end )
From 1のデータ
Group By グループ

 

そして、1の連番を振るところが、データベース毎に工夫が必要になります。

 

Window関数が使える場合

Select
グループ
,担当
,row_number() over(partition by グループ order by 担当 asc)
From 縦持ちのデータ

連番を振るWindow関数はrow_numberです。OracleSQLServer、Postgresなどメジャーなデータベースはこれを使うと便利です。

スカラ副問合せが使える場合

Select
グループ
,担当
,(select count(*) from 縦持ちのデータ スカラ
where データ.グループ = スカラ.グループ
and データ.担当 >= スカラ.担当) as 連番
From
縦持ちのデータ データ

この書き方で同じような結果がでます。気をつけたいのが同じグループ内に担当が重複していると正しい連番になりません。事前に重複を排除するなどが必要です。

それ以外(主にMS-ACCESS

Select
データ.グループ
,データ.担当
,count(*)
From
縦持ちのデータ データ
inner join
縦持ちのデータ 連番用
on データ.グループ = 連番用.グループ
and データ.担当 >= 連番用.担当
group by
データ.グループ
,データ.担当

SQLパズルのようになりますが、MS-ACCESSではこうやらざると得ないと思っています。こちらも同じグループ内で担当が重複してると正しい連番になりません。事前に重複を排除するなどが必要です。

 

縦横変換も、月ごとに横に並べる場合などは、最初から月を場合分けの条件にすればいいのですが、場合分けの条件にする項目が用意されていない場合は、連番を振る必要があります。その時には上記のいずれかを使うことになると思います。

Salesforce: ナレッジを一括で公開する

Salesforceのナレッジをまとめて登録し、そのまま公開したい場合があります。

その際のやり方は次のようになります。

 

ナレッジをまとめて登録する

データローダをはじめとするAPIで登録が可能です。

f:id:crmprogrammer38:20170731182247p:plain

上記の、ナレッジの記事タイプの詳細で表示されているAPI参照名がオブジェクトAPI名となり、通常のオブジェクトの同じように登録を行います。上の画面では「type1__kav」がオブジェクトAPI名です。

そして登録後は「ドラフト記事」として保存されます。データローダ(API)で「type1__kav」をselectすると「ドラフト記事」は取得できないため、「type1__kav」にinsert後、「type1__kav」をselectすると結果に表示されません。、公開状況(PublishStatus)に"Draft"の検索条件を指定しないと結果が取得できません。他のオブジェクトの違いに多少戸惑います。

2017/8/17:__kavのオブジェクトからデータを取得する際には、公開状況(PublishStatus)に何も検索条件を指定しないと、公開状況(PublishStatus)が"Online"の条件がデフォルトとなることがわかりました。記載が間違えていたので訂正しました。

 

余談:ナレッジをまとめて削除する

「type1__kav」は、insert、upsert、updateが可能で、deleteはできません。deleteは「type1__ka」で行います。

 

ナレッジをまとめて公開する

f:id:crmprogrammer38:20170731183644p:plain

画面から行う場合は、「記事の管理」タブのドラフト記事から、複数のナレッジにチェックを入れて公開ボタンをクリックします。

それとは別にナレッジ用に用意されているApexClassからも公開できます。

List<KnowledgeArticle> rows = [Select  Id  From   KnowledgeArticle
where id not in ( select KnowledgeArticleId from KnowledgeArticleVersion) limit 100];
[Select KnowledgeArticleId From KnowledgeArticleVersion where PublishStatus = 'Draft' limit 100 ]

for( KnowledgeArticle row : rows ){
KbManagement.PublishingService.publishArticle(row.Id, true);
}

上記のコードで公開ができます。コードでKnowledgeArticle、KnowledgeArticleVersionを使いましたが、type1__ka、type1__kavとしても同じです。
※2017/8/17 公開状況(PublishStatus)に"Draft"の検索条件を入れれば、KnowledgeArticleVersionから取得できることがわかりSOQLを修正しました。

KnowledgeArticle、KnowledgeArticleVersionは全ての記事タイプを通してナレッジを管理できるオブジェクトです。

soqlでlimitを入れていますが、使ってみた結果100件が限界でした。ですので、100件以上まとめて公開する場合は、上記を全て公開できるまで繰り返すことになります。

 

Salesforce: SOQLの検索条件で、半角全角の区別ではまったこと

SOQLで検索条件は、半角全角の区別ではまりました。
何ではまったかというと、検索条件で指定したのは半角なのに、検索結果に全角も含まれていたからです。

 

はまった経緯は次です。

  • 半角の"x01"と全角の"x01"は分けてコード登録を行います。
  • Apex処理内でコードを使って別のデータとのひもづけを行う処理を作ろうとしました。

 

はまった結果は次です。

  • Apex内で半角の"x01"にひもづけてレコードを作成しようとして、半角の"x01"を検索条件としてSOQLを実行しました。ですが、その結果に、全角の"x01"も含まれていて、本来であればひもづけは不要な全角の"x01"にもレコードをひもづけてしまいました。

 

Salesforceでの半角全角・大文字小文字の区別

f:id:crmprogrammer38:20170728084239p:plain

ユニーク項目で、上記の「大文字と小文字を区別する」にすると、大文字小文字は区別されてますが、半角・全角の区別はありません。

※ただし、半角カナと全角カナは異なる文字として扱われます。

逆にいうとユニーク項目でない場合や、ユニーク項目でも上記の「大文字と小文字を区別しない」にチェックしていると、半角全角と大文字小文字が区別されません。

 

どう対処するか

  1. SOQLで検索結果に対して再度、指定した条件でフィルタする処理を作る。(Apexでの文字の比較は半角全角・大文字小文字は区別します)
  2. もしくはSalesforce以外で、要件を満たす半角全角・大文字小文字の区別ができるデータベースなどを用意してそちらの処理結果をSalesforceに登録する。

2での対策は、Salesforce以外にもサーバが必要とし、余計面倒ですので、1の検索結果にさらにフィルタする処理を作るのが現実的と思います。

 

Salesforceでキーとして扱う項目は、半角大文字で統一するなどルールを決めておくのが一番かなと思いました。

Salesforce: パーセント項目をApexで普通に掛け算すると100倍になる

Salesforceのパーセント項目の使い方を間違って値が100倍になってました。

間違った理由は、数値項目とパーセント項目を単純に掛け算したからで、正しい使い方は、数値項目とパーセント項目を掛け算した後、100で割る必要があります。

 

例えば、「24%」 というパーセンテージがあった場合、SOQLでパーセント項目を取得すると結果は 「24」です。

数式項目は、戻り値のデータ型で表示が変わったりするので、使い方のメモになります。

 

「24%」をサンプルの値とした場合の各パターン別の値

登録/参照パターン
データローダ(API)で登録する 24
データローダ(API)で取得する 24
画面で登録する 24
画面で参照する 24%
数式:戻り値のデータ型がパーセント 24%
数式:戻り値のデータ型が数値 0.24
Apexで登録する 24
Apexで取得する 24

 

こう書くと、Apexの中でSOQLで取得結果で掛け算する時は100で割らないといけないのはわかりますが、Excelで慣れてるからか、100で割るのを忘れてしまいます。。

Excel: 複数のExcelファイルのセルに値をまとめてセットするマクロを作りました

同じフォーマットのExcelファイルが複数あって、同じ場所のセルにまとめて更新したい時があります。

例えば、DBのテーブル定義書をテーブル単位にファイルを分けていて、各テーブルのラベルや物理名を特定のセルに入れる場合などがあると思います。

 

そんな時に使えるマクロを作りました。

こちらからダウンロードできます。

 

マクロの画面イメージ

f:id:crmprogrammer38:20170726135539p:plain

 

使い方は、対象のExcelファイル名(フルパス)、シート番号/シート名、セルアドレス、設定値をしておくとまとめてExcelを更新します。

Excelの列は次の通りです。

Excelファイル(フルパスで指定) 更新したいExcelファイルのフルパス
シート番号(1~) または、シート名 1から連番のシート番号、もしくはSheet1などのシート名
セル位置(例.A2など) セルのアドレス
設定値 設定したい値
処理結果 正常に実行できた場合は、"正常に書き込みました。"となり、
それ以外はエラーメッセージとなります。

 

Salesforce: 自動採番項目をupsertの外部ID(externalIdField)として使う

Salesforceで自動採番項目を外部IDにできるようになりました。

Apex内でupsertのキーとして使用できるようになりましたが、DataLoaderをGUIで使う時はupsertの外部IDとして選ぶことができません。

 

Help | Training | Salesforceの記載には、

データローダは ID 項目、あるいは、外部 ID 項目を API の Describe コマンドの結果より、
作成可能な、または、更新可能な項目を表示します。
しかしながら、自動採番の項目が作成可能、または、更新可能ではないので、データローダには表示されません。
これは、ユーザが潜在的な Insert エラーを実行しないようにするための意図的なものです。

 と書いてます。

 

ですが、DataLoaderのコマンドライン操作では、問題なくupsertの外部IDとして、自動採番項目が使えます。(もちろん、自動採番項目で登録されてない値でupsertするとエラーになります)