プログラマ38の日記

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

SQL: よくやってしまう間違い(割り算や型変換)

気を抜くとすぐ忘れてしまうのでメモ。(メモしたから忘れないわけではないですが)

書き方の例でnullif関数を使っていますが、適宜データベースで用意されている関数に置き換えが必要です。

SQLの割り算では分母が0の時はnullに変換する

0除算エラーの対策です。もちろん固定値で割る場合は不要なのですが、項目の値や、集計値で除算する際には必須です。

--f2に0が入っていてもエラーを発生させないようにする
select f1 / nullif(f2, 0) from sample

 

数値や日付の変換では、空文字はnullに変換する

--f3に空文字が入っていてもエラーを発生させないようにする
select cast( nullif(f3,'') as decimal(18,0) ) from sample
select cast( nullif(f4,'') as date ) from sample

 

OracleJDBCでTimestamp型をgetObjectで取得しない

JDBCのResultsetで、getObjectはとても便利で、select結果の型に応じた項目値を返却してくれます。ですが、OracleのTimestamp型をgetObjectすると、java.sql.Timestampではなく、oracle.sql.TIMESTAMPが返却されてくるので、うっかりjava.sql.Timestampにキャストするとエラーになります。

きちんとgetTimestampを使って取得する必要があります。

 

私事

Oracleは、日付型の実装はしないんですかね。

Dateの項目を検索する際に、trunc(datefield) とかをやるくらいなら、日付型があれば便利だと思います。インデックスとかも使いやすくなりますし。

VARCHAR2とかあるので、DATE2とかで作ってくれてもいいのになーとか思いました。

Salesforce: 直接XMLでSalesforce APIを操作する

WebServiceSOAPメッセージのデバッグについて書きました。

 

crmprogrammer38.hatenablog.com

 

これを行うことで、SOAPメッセージが拾えるので、xmlをpostして、xmlで結果が返ってくるというシンプルな処理でSOAP APIを行うことができます。wsdlクライアントが使えない場合でも、SOAP APIを操作することができるようになります。

 

例えば、たくさんリストビューを作る作業を考えます。

それぞれのリストビューは「ビュー名」、「一意の名前」、「条件」、「公開先のグループ」が異なるものとします。

1.雛形とするビューを作成します。

2.一度xmlを取得するため次のプログラムを実行します。(wscでの作成しました)

import com.sforce.soap.metadata.ListView;
import com.sforce.soap.metadata.Metadata;
import com.sforce.soap.metadata.MetadataConnection;
import com.sforce.soap.metadata.ReadResult;
import com.sforce.soap.metadata.UpsertResult;
import com.sforce.soap.partner.LoginResult;
import com.sforce.soap.partner.PartnerConnection;
import com.sforce.ws.ConnectorConfig;


public class DebugSoapMessage {

public static void main(String[] args) throws Exception{

ConnectorConfig soapConfig = new ConnectorConfig();
soapConfig.setManualLogin(true);
soapConfig.setPrettyPrintXml(true);
//soapConfig.setTraceMessage(true);
soapConfig.setServiceEndpoint("https://login.salesforce.com/services/Soap/u/40.0");

PartnerConnection pCon = new PartnerConnection(soapConfig);

LoginResult lResult = pCon.login("sample@sample.user", "samplepassword");
pCon.setSessionHeader(lResult.getSessionId());
soapConfig.setServiceEndpoint(lResult.getServerUrl());


ConnectorConfig metaConfig = new ConnectorConfig();
metaConfig.setServiceEndpoint(lResult.getMetadataServerUrl());
metaConfig.setSessionId(lResult.getSessionId());
metaConfig.setPrettyPrintXml(true);

MetadataConnection mCon = new MetadataConnection(metaConfig);
//雛形のビューを指定する。
ReadResult rResult = mCon.readMetadata("ListView", new String[]{"Object1__c.v01"});

ListView lView = (ListView)rResult.getRecords()[0];

//upsert時に送るSOAPメッセージをデバッグ
metaConfig.setTraceMessage(true);

UpsertResult[] result = mCon.upsertMetadata(new Metadata[]{lView});
}
}

3.実行結果のxmlを取得します

WSC: Creating a new connection to https://insntanceurl/services/Soap/m/40.0/metaurlsuffix Proxy = DIRECT username null
------------ Request start ----------
<?xml version="1.0" encoding="UTF-8"?><env:Envelope
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Header>
<SessionHeader xmlns="http://soap.sforce.com/2006/04/metadata">
<sessionId>sessionidstring</sessionId>
</SessionHeader>
</env:Header>
<env:Body>
<m:upsertMetadata xmlns:m="http://soap.sforce.com/2006/04/metadata" xmlns:sobj="null">
<m:metadata xsi:type="m:ListView">
<m:fullName>Object1__c.v01</m:fullName>
<m:booleanFilter>1 AND (2 or 3)</m:booleanFilter>
<m:columns>NAME</m:columns>
<m:columns>TextField__c</m:columns>
<m:columns>ango__c</m:columns>
<m:columns>NumberField__c</m:columns>
<m:filterScope>Mine</m:filterScope>
<m:filters>
<m:field>TextField__c</m:field>
<m:operation>notEqual</m:operation>
</m:filters>
<m:filters>
<m:field>NumberField__c</m:field>
<m:operation>equals</m:operation>
</m:filters>
<m:filters>
<m:field>NumberField__c</m:field>
<m:operation>greaterThan</m:operation>
<m:value>100</m:value>
</m:filters>
<m:label>ビューラベル名</m:label>
<m:language>ja</m:language>
<m:sharedTo>
<m:allInternalUsers></m:allInternalUsers>
</m:sharedTo>
</m:metadata>
</m:upsertMetadata>
</env:Body>
</env:Envelope>
------------ Request end ----------
Transfer-Encoding=[chunked]
null=[HTTP/1.1 200 OK]
Content-Encoding=[gzip]
Vary=[Accept-Encoding]
Set-Cookie=[BrowserId=pEJTCUgNTt-2qJS20bKByA;Path=/;Domain=.salesforce.com;Expires=Fri, 06-Oct-2017 03:45:53 GMT]
Expires=[Thu, 01 Jan 1970 00:00:00 GMT]
Date=[Mon, 07 Aug 2017 03:45:53 GMT]
Content-Type=[text/xml; charset=utf-8]
------------ Response start ----------
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://soap.sforce.com/2006/04/metadata">
<soapenv:Body>
<upsertMetadataResponse>
<result>
<created>false</created>
<fullName>Object1__c.v01</fullName>
<success>true</success>
</result>
</upsertMetadataResponse>
</soapenv:Body>
</soapenv:Envelope>
------------ Response end ----------

4.3で取得したリクエストのxmlで置き換えたい箇所の値を変えれば、同じようなリストビューを機械的に作成できます。置き換える値(変える可能性の高い値)は赤文字にしています。

Excelのマクロで"WinHttp.WinHttpRequest"を使うと便利です。

もちろん、マクロ内では、endponitのURLの指定と共に、httpヘッダに

Content-Type: text/xml; charset=utf-8
SOAPAction: ""

の指定が必要です。(Excelマクロでtls1.1以上の通信ができる前提ですが)

 

所感

例としてリストビューでしたが、カスタム項目のxmlなど様々なxmlを取得することで便利なツールをExcelマクロで作成できるようになります。

複雑な設定に対応しようとするとマクロも複雑になるのである程度シンプルなものだけツールにして、それ以外は手動で画面から作成するのがいいのだと思います。

 

Java: JavaのWebServiceライブラリのクライアントプログラムでSOAPメッセージをデバッグする

前に、いくつかのJavaライブラリでSalesforceSOAP APIを扱うという内容を書きました。

 

crmprogrammer38.hatenablog.com

 

さらに追記で、上記のJavaライブラリでSOAPメッセージをデバッグするやり方になります。

 

Axis1.4

log4j.propertiesで指定します。なので、この呼び出しだけdebugするということができません。
また、debugにすると非常に処理時間が遅くなります。

log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.Threshold=DEBUG
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

Axis2

log4j.propertiesで指定します。なので、この呼び出しだけdebugするということができません。Axis1.4ほど処理時間が遅くなることはありません。

log4j.logger.httpclient.wire.header=DEBUG
log4j.logger.httpclient.wire.body=DEBUG

wsimport

システムプロパティで指定します。プログラム中でも、javaコマンドの引数でも可能です。

    System.setProperty("com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump", "true");
//2021/12/28 追記 上記でうまく動作しないことがあり次のようにコードでダイレクトに指定した。
   com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump = true;

apache cxf

プログラム内で指定します。

    Client client = ClientProxy.getClient(bp);
    client.getOutInterceptors().add(new LoggingOutInterceptor());
    client.getInInterceptors().add(new LoggingInInterceptor());

wsc

プログラム内で指定します。

    ConnectorConfig connectorConfig = new ConnectorConfig();
    connectorConfig.setPrettyPrintXml(true);
    connectorConfig.setTraceMessage(true);

 

 

上記でhttpヘッダ、SOAPメッセージがデバッグできます。リクエストに対してのレスポンスをxmlレベルで把握することで、例えば障害時の調査に役立ったりすると思います。

また、上記のxmlがわかればwsdlをプログラムコードに展開できないアーキテクチャでもhttp通信さえできればxmlでのやり取りでSOAP APIを利用することができます。

次の記事でxmlのやり取りでSalesforceSOAP APIを利用するサンプルを書きたいと思います。

Java: 「Apache Velocity」と「StringTemplate」の性能を比べてみる

前回StringTemplateの記事を書きましたが、Apache Velocityと性能を比べてみました。

 

Apache Velocityではまったこと

テンプレートの書き方を忘れていて、テンプレートで変数のプロパティを指定する場合getterが必要でした。。

例えば、Apache Velocityのテンプレートで、${obj.value01}とした場合、プログラム内では、public変数の"public String value01;" では駄目で、メソッドの "public String getValue01() "が必要となります。

(まぁ、Javaコードならgetterは作るとは思いますが、こういう点では「StringTemplate」の柔軟さはいいですね。)

比較結果

出力の時間を比較してみました。

結論としては「Apache Velocity」が早いです。1回目と2回目はちょっと遅いですが、その後の性能が「String Template」の半分の時間で終わっています。扱う件数が1億件とかになってくるとこの処理時間の差で1日ぐらいの差となります。

実行回数 StringTemlate Apache Velocity 差(A-B)
ナノ秒(A) ナノ秒(B)
1 8,810,818 22,477,063 -13,666,245
2 2,216,037 2,628,724 -412,687
3 1,696,282 728,150 968,132
4 1,775,865 653,078 1,122,787
5 1,458,761 719,125 739,636
6 1,586,751 516,474 1,070,277
7 1,512,911 408,584 1,104,327
8 1,441,531 471,759 969,772
9 1,192,115 471,759 720,356
10 1,180,628 430,326 750,302
2から10の平均 1,562,320 780,887 781,434

 

 Apache Velocityのテンプレート

StringTemplateと同様のものをVTLで書きました。「Apache Velocity」のバージョンは1.7を使います。

${callcnt} 回目の出力

変数の差込10個

01 ${value01}
02 ${value02}
03 ${value03}
04 ${value04}
05 ${value05}
06 ${value06}
07 ${value07}
08 ${value08}
09 ${value09}
10 ${value10}

プロパティの差込10個 (1段階)
01 ${obj.value01}
02 ${obj.value02}
03 ${obj.value03}
04 ${obj.value04}
05 ${obj.value05}
06 ${obj.value06}
07 ${obj.value07}
08 ${obj.value08}
09 ${obj.value09}
10 ${obj.value10}

プロパティの差込10個 (2段階)
01 ${obj.prop.value01}
02 ${obj.prop.value02}
03 ${obj.prop.value03}
04 ${obj.prop.value04}
05 ${obj.prop.value05}
06 ${obj.prop.value06}
07 ${obj.prop.value07}
08 ${obj.prop.value08}
09 ${obj.prop.value09}
10 ${obj.prop.value10}

ループ(要素10)
#foreach( $row in $rows ) 
${row.rownum} ${row.value
#end

そして性能を算出したプログラムは次です。

通常「Apache Velocity」はテンプレートファイルから読み込むのですが、「String Template」と同様にテンプレートの文字列から変数を展開するように書いています。

import java.io.File;
import java.io.FileInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.util.ArrayList;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.runtime.parser.ParseException;
import org.apache.velocity.runtime.parser.node.SimpleNode;

public class VelocitySample {
  
  public static void main(String... args) throws Exception{
    DecimalFormat df = new DecimalFormat("000000");
    
    Template vt = null;
    
    long templatestart = System.nanoTime();
    vt = newTemplate(templateStr());
    long templateend = System.nanoTime();
    System.out.println(templateend - templatestart);

    
    for( int i=1; i<=1000 ; i++) {
      long bindstart = System.nanoTime();    
      new VelocitySample().execute(vt ,i);
      long bindend = System.nanoTime();
      System.out.println( df.format(i) + "回目:" + (bindend - bindstart));
    }    
  }
  
  public static Template newTemplate(String templateString) throws Exception{

/** このように書くことでテンプレートの文字列を内部でコンパイルする **/

    RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();
    StringReader reader = new StringReader(templateString);
    SimpleNode node;
    try {
      node = runtimeServices.parse(reader, "template1");
    } catch (ParseException e) {
      throw new RuntimeException("パースエラー", e);
    }

    Template template = new Template();
    template.setRuntimeServices(runtimeServices);
    template.setData(node);
    template.initDocument();

    return template;
  }
  
  
  public  void execute(Template vt , int callcnt) throws Exception{
    
    
    String rnd = ":" + (int)(Math.random() * 10000);
    
    String value01 = "value01"+rnd;
    String value02 = "value02"+rnd;
    String value03 = "value03"+rnd;
    String value04 = "value04"+rnd;
    String value05 = "value05"+rnd;
    String value06 = "value06"+rnd;
    String value07 = "value07"+rnd;
    String value08 = "value08"+rnd;
    String value09 = "value09"+rnd;
    String value10 = "value10"+rnd;


    Class1 c1 = new Class1();
    c1.value01 = "nest1obj." + value01;
    c1.value02 = "nest1obj." + value02;
    c1.value03 = "nest1obj." + value03;
    c1.value04 = "nest1obj." + value04;
    c1.value05 = "nest1obj." + value05;
    c1.value06 = "nest1obj." + value06;
    c1.value07 = "nest1obj." + value07;
    c1.value08 = "nest1obj." + value08;
    c1.value09 = "nest1obj." + value09;
    c1.value10 = "nest1obj." + value10;


    c1.prop.value01 = "nest21obj." + value01;
    c1.prop.value02 = "nest21obj." + value02;
    c1.prop.value03 = "nest21obj." + value03;
    c1.prop.value04 = "nest21obj." + value04;
    c1.prop.value05 = "nest21obj." + value05;
    c1.prop.value06 = "nest21obj." + value06;
    c1.prop.value07 = "nest21obj." + value07;
    c1.prop.value08 = "nest21obj." + value08;
    c1.prop.value09 = "nest21obj." + value09;
    c1.prop.value10 = "nest21obj." + value10;

    ArrayList<Class2> rows = new ArrayList<>();
    
    for(int i=1; i<=10; i++){
      Class2 c2 = new Class2();
      c2.rownum = i;
      c2.value = "loopvalue" + rnd;
      rows.add(c2);
    }
    
    
    VelocityContext vc = new VelocityContext();
    
    
    
    vc.put("callcnt",callcnt );
    vc.put("value01",value01 );
    vc.put("value02",value02 );
    vc.put("value03",value03 );
    vc.put("value04",value04 );
    vc.put("value05",value05 );
    vc.put("value06",value06 );
    vc.put("value07",value07 );
    vc.put("value08",value08 );
    vc.put("value09",value09 );
    vc.put("value10",value10 );

    vc.put("obj", c1);
    vc.put("rows", rows);
    
    StringWriter writer = new StringWriter();
    vt.merge( vc , writer);
    
    String ret = writer.toString();
    
  }
  
  
  
  
  public  class Class1{
    public String getValue01() {
      return value01;
    }

    public void setValue01(String value01) {
      this.value01 = value01;
    }

    public String getValue02() {
      return value02;
    }

    public void setValue02(String value02) {
      this.value02 = value02;
    }

    public String getValue03() {
      return value03;
    }

    public void setValue03(String value03) {
      this.value03 = value03;
    }

    public String getValue04() {
      return value04;
    }

    public void setValue04(String value04) {
      this.value04 = value04;
    }

    public String getValue05() {
      return value05;
    }

    public void setValue05(String value05) {
      this.value05 = value05;
    }

    public String getValue06() {
      return value06;
    }

    public void setValue06(String value06) {
      this.value06 = value06;
    }

    public String getValue07() {
      return value07;
    }

    public void setValue07(String value07) {
      this.value07 = value07;
    }

    public String getValue08() {
      return value08;
    }

    public void setValue08(String value08) {
      this.value08 = value08;
    }

    public String getValue09() {
      return value09;
    }

    public void setValue09(String value09) {
      this.value09 = value09;
    }

    public String getValue10() {
      return value10;
    }

    public void setValue10(String value10) {
      this.value10 = value10;
    }

    public Nest1 getProp() {
      return prop;
    }

    public void setProp(Nest1 prop) {
      this.prop = prop;
    }

    public String value01;
    public String value02;
    public String value03;
    public String value04;
    public String value05;
    public String value06;
    public String value07;
    public String value08;
    public String value09;
    public String value10;
    
    public Nest1 prop = new Nest1();
  }
  
  public  class Nest1{
    public String getValue01() {
      return value01;
    }
    public void setValue01(String value01) {
      this.value01 = value01;
    }
    public String getValue02() {
      return value02;
    }
    public void setValue02(String value02) {
      this.value02 = value02;
    }
    public String getValue03() {
      return value03;
    }
    public void setValue03(String value03) {
      this.value03 = value03;
    }
    public String getValue04() {
      return value04;
    }
    public void setValue04(String value04) {
      this.value04 = value04;
    }
    public String getValue05() {
      return value05;
    }
    public void setValue05(String value05) {
      this.value05 = value05;
    }
    public String getValue06() {
      return value06;
    }
    public void setValue06(String value06) {
      this.value06 = value06;
    }
    public String getValue07() {
      return value07;
    }
    public void setValue07(String value07) {
      this.value07 = value07;
    }
    public String getValue08() {
      return value08;
    }
    public void setValue08(String value08) {
      this.value08 = value08;
    }
    public String getValue09() {
      return value09;
    }
    public void setValue09(String value09) {
      this.value09 = value09;
    }
    public String getValue10() {
      return value10;
    }
    public void setValue10(String value10) {
      this.value10 = value10;
    }
    public String value01;
    public String value02;
    public String value03;
    public String value04;
    public String value05;
    public String value06;
    public String value07;
    public String value08;
    public String value09;
    public String value10;

  }
  
  public  class Class2 {
    public int getRownum() {
      return rownum;
    }
    public void setRownum(int rownum) {
      this.rownum = rownum;
    }
    public String getValue() {
      return value;
    }
    public void setValue(String value) {
      this.value = value;
    }
    public int rownum;
    public String value;
  }
  
  public static String templateStr() throws Exception{
    File t = new File("template1.txt");
    
    byte[] bytes = new byte[(int)t.length()];
    
    FileInputStream fis = new FileInputStream(t);
    
    fis.read(bytes);
    
    fis.close();
    
    return new String(bytes, "Windows-31J");
  }
}

setter/getterを追加しているのもそうですが、「Apache Velocity」用のコードだけでも多いですね。

Java: 「StringTemplate」ライブラリを使ってみました

Javaで、「StringTemplate」のライブラリを使ってみました。

 

はじめに

Javaのテンプレートエンジンは、Apache Velocityを代表として色々なものがあると思います。VelocityはVTLが使いやすいのですが様々な理由でVelocity以外が使いたいときもあると思います。例えば、Apache Velocityで指定されているjarの別のバージョンを既に使っていて、検証や調査が必要となる場合などが考えられます。

 

ただ、テンプレートの文字列とバインドする変数があればいいという人には「StringTemplate」は便利だと思います。長いSQLの一部分だけ変数を差し込みたいときなど重宝します。

 

今回、「StringTemplate」のv3を選択しました。

 

テンプレートを用意

こんなテンプレートを作りました。基本バインド変数は$で囲む書き方です。

文字として$を使う場合は「\$」とします。シンプルで良いと思いました。もちろんIFなどの関数も用意されています。

$callcnt$ 回目の出力

変数の差込10個

01 $value01$
02 $value02$
03 $value03$
04 $value04$
05 $value05$
06 $value06$
07 $value07$
08 $value08$
09 $value09$
10 $value10$

プロパティの差込10個 (1段階)
01 $obj.value01$
02 $obj.value02$
03 $obj.value03$
04 $obj.value04$
05 $obj.value05$
06 $obj.value06$
07 $obj.value07$
08 $obj.value08$
09 $obj.value09$
10 $obj.value10$

プロパティの差込10個 (2段階)
01 $obj.prop.value01$
02 $obj.prop.value02$
03 $obj.prop.value03$
04 $obj.prop.value04$
05 $obj.prop.value05$
06 $obj.prop.value06$
07 $obj.prop.value07$
08 $obj.prop.value08$
09 $obj.prop.value09$
10 $obj.prop.value10$

ループ(要素10)
$rows:{ row | $row.rownum$ $row.value
}$

Javaプログラムの書き方

    StringTemplate st = new StringTemplate();
    st.setTemplate("テンプレートの文字列");

    st.reset(); //バインド変数をクリア
    st.setAttribute("callcnt",callcnt );
    st.setAttribute("value01",value01 );
    st.setAttribute("value02",value02 );
    st.setAttribute("value03",value03 );
    st.setAttribute("value04",value04 );
    st.setAttribute("value05",value05 );
    st.setAttribute("value06",value06 );
    st.setAttribute("value07",value07 );
    st.setAttribute("value08",value08 );
    st.setAttribute("value09",value09 );
    st.setAttribute("value10",value10 );

    st.setAttribute("obj", c1);
    st.setAttribute("rows", rows);
    
    String ret = st.toString(); //バインド変数を展開して出力

変数をバインドする箇所が長いですが、「StringTemplate」だけの操作は非常に少ないです。

性能について

1度目だけ遅くて、それ以外は早いです。またテンプレートの文字列を変えても早いです。テンプレートからバイトコードに変換していると思いますが凄いです。安心して利用できます。

あくまで参考ですが、こんな性能になりました。

実行回数 ナノ秒
1 8810818
2 2216037
3 1696282
4 1775865
5 1458761
6 1586751
7 1512911
8 1441531
9 1192115
10 1180628

上記の性能を算出した際のテンプレートは上記に記載したテンプレートで、プログラムは次の通りです。



import java.io.File;
import java.io.FileInputStream;
import java.text.DecimalFormat;
import java.util.ArrayList;


import org.antlr.stringtemplate.StringTemplate;


public class STSample {
  

  
  
  public static void main(String... args) throws Exception{
    
    DecimalFormat df = new DecimalFormat("000000");
    
    StringTemplate st = new StringTemplate();
    
    
    
    long templatestart = System.nanoTime();
    st.setTemplate(templateStr());
    long templateend = System.nanoTime();
    System.out.println(templateend - templatestart);

    
    for( int i=1; i<=1000 ; i++) {
      long bindstart = System.nanoTime();    
      new STSample().execute(st, i );
      long bindend = System.nanoTime();
      System.out.println( df.format(i) + "回目:" + (bindend - bindstart));
      
      
    }
    
  }


  public  void execute(StringTemplate st , int callcnt) throws Exception{
    
    
    String rnd = ":" + (int)(Math.random() * 10000);
    
    String value01 = "value01"+rnd;
    String value02 = "value02"+rnd;
    String value03 = "value03"+rnd;
    String value04 = "value04"+rnd;
    String value05 = "value05"+rnd;
    String value06 = "value06"+rnd;
    String value07 = "value07"+rnd;
    String value08 = "value08"+rnd;
    String value09 = "value09"+rnd;
    String value10 = "value10"+rnd;


    Class1 c1 = new Class1();
    c1.value01 = "nest1obj." + value01;
    c1.value02 = "nest1obj." + value02;
    c1.value03 = "nest1obj." + value03;
    c1.value04 = "nest1obj." + value04;
    c1.value05 = "nest1obj." + value05;
    c1.value06 = "nest1obj." + value06;
    c1.value07 = "nest1obj." + value07;
    c1.value08 = "nest1obj." + value08;
    c1.value09 = "nest1obj." + value09;
    c1.value10 = "nest1obj." + value10;


    c1.prop.value01 = "nest21obj." + value01;
    c1.prop.value02 = "nest21obj." + value02;
    c1.prop.value03 = "nest21obj." + value03;
    c1.prop.value04 = "nest21obj." + value04;
    c1.prop.value05 = "nest21obj." + value05;
    c1.prop.value06 = "nest21obj." + value06;
    c1.prop.value07 = "nest21obj." + value07;
    c1.prop.value08 = "nest21obj." + value08;
    c1.prop.value09 = "nest21obj." + value09;
    c1.prop.value10 = "nest21obj." + value10;

    ArrayList<Class2> rows = new ArrayList<>();
    
    for(int i=1; i<=10; i++){
      Class2 c2 = new Class2();
      c2.rownum = i;
      c2.value = "loopvalue" + rnd;
      rows.add(c2);
    }
    
    
    st.reset();
    
    
    st.setAttribute("callcnt",callcnt );
    st.setAttribute("value01",value01 );
    st.setAttribute("value02",value02 );
    st.setAttribute("value03",value03 );
    st.setAttribute("value04",value04 );
    st.setAttribute("value05",value05 );
    st.setAttribute("value06",value06 );
    st.setAttribute("value07",value07 );
    st.setAttribute("value08",value08 );
    st.setAttribute("value09",value09 );
    st.setAttribute("value10",value10 );

    st.setAttribute("obj", c1);
    st.setAttribute("rows", rows);
    
    String ret = st.toString();
    

    



  }
  
  public  class Class1{
    public String value01;
    public String value02;
    public String value03;
    public String value04;
    public String value05;
    public String value06;
    public String value07;
    public String value08;
    public String value09;
    public String value10;
    
    public Nest1 prop = new Nest1();
  }
  
  public  class Nest1{
    public String value01;
    public String value02;
    public String value03;
    public String value04;
    public String value05;
    public String value06;
    public String value07;
    public String value08;
    public String value09;
    public String value10;

  }
  
  
  
  public  class Class2 {
    public int rownum;
    public String value;
  }
  
  public static String templateStr() throws Exception{
  //上記のテンプレートをsample1.txtでshift_jisで保存しておく
    File t = new File("template1.txt");
    
    byte[] bytes = new byte[(int)t.length()];
    
    FileInputStream fis = new FileInputStream(t);
    
    fis.read(bytes);
    
    fis.close();
    
    return new String(bytes, "Windows-31J");
  }
}

 

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ではこうやらざると得ないと思っています。こちらも同じグループ内で担当が重複してると正しい連番になりません。事前に重複を排除するなどが必要です。

 

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