GROUP_CONCAT を書く時の注意とメモ。

たまにしか書かない、GROUP_CONCAT の書式をよく忘れるのでメモ。

GROUP_CONCAT( 列名 [ ORDER  BY  順序つける列名 ] [ SEPARATOR 区切り文字] )

注意しなければならないのは CONCAT対象の列の値が NULL の場合、

SEPARATOR を指定しても区切り文字もつかず全てNULLだと空文字になってしまう。

 SEPARATOR ',' を指定しても全てNULLは期待する値 ",,,," のようにならない。

よって、

GROUP_CONCAT( COALESCE(列名 , 'NULL' SEPARATOR ',') 

のようにCOALESCE で逃げないと、正確な区切りを後で判定きない。

 

 

MySQL Error code 1418 , ストアドFUNCTION 作成で、

MySQL で ストアドFUNCTION 作成で、

Error Code: 1418. This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)

が出たら、

DETERMINISTICNO SQL、または READS SQL DATA のいずれかを CREATE FUNCTION ~BEGINの間に指定しないとならない。

詳しくは、、MySQLのマニュアルで。

 

CREATE FUNCTION  somefunc (a INT) RETURN  INT

DETERMINISTIC

BEGIN

 

 

リストの比較、ListDiff

6年も前は、リストを比較して処理するのに、ブログ投稿したことを考えてた

http://oboe2uran.hatenablog.com/entry/2010/07/01/121003http://oboe2uran.hatenablog.com/entry/2010/07/01/121003


http://oboe2uran.hatenablog.com/entry/2010/06/26/132425http://oboe2uran.hatenablog.com/entry/2010/07/01/121003

でも、最近は、以下のように書くことが多くなった。

リスト、alist , blist がある時、

alist.stream().filter(e->!blist.contains(e)).forEach(e->{
   // alist だけに存在する処理
});

blist.stream().filter(e->!alist.contains(e)).forEach(e->{
   // blist だけに存在する処理
});

alist.stream().filter(e->blist.contains(e)).forEach(e->{
   // alist と blist 両方に存在する処理
});

でも、これは、filter(Predicate) を3回も注意して記述しなければならないし、contains で問い合わせて効率が
悪そう。

そこで考えたのが、
① リスト要素を等しいと判定する BiFunction
② 各々のリストだけに存在する要素に対する Consumer
③ 両方のリストに存在する要素に対する Consumer
を指定して比較処理を普遍的に実行すること。

import java.util.AbstractMap;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
/**
 * リストの比較.
 *
 * ListDiff.of で、比較する2つのリスト要素を等しいと判定する BiFunction を指定してインスタンスを生成
 * 3つのメソッドの振る舞いを登録した後、diff メソッドを実行する。
 *
 * leftOnly  : 左側(1番目指定)リストだけに存在する要素に対する Consumer
 * rightonly : 右側(2番目指定)リストだけに存在する要素に対する Consumer
 * match     ; 両方のリストに存在する要素に対する BiConsumer
 *
 */
public final class ListDiff<T>{
	private BiFunction<T, T, Boolean> matchFunction;
	private Consumer<T> leftonly;
	private Consumer<T> rightonly;
	private BiConsumer<T, T> biConsumer;

	private ListDiff(BiFunction<T, T, Boolean> matchFunction){
		this.matchFunction = matchFunction;
	}
	
	public static <T> ListDiff<T> of(BiFunction<T, T, Boolean> matchFunction){
		 return new ListDiff<>(matchFunction);
	}
	public void leftOnly(Consumer<T> leftonly){
		this.leftonly = leftonly;
	}
	public void rightOnly(Consumer<T> rightonly){
		this.rightonly = rightonly;
	}
	public void match(BiConsumer<T, T> biConsumer){
		this.biConsumer = biConsumer;
	}
	public void diff(List<T> leftList, List<T> rightList){
		if (leftonly != null)
			leftList.stream().filter(e->rightList.stream()
			.noneMatch(t->matchFunction.apply(t, e))).forEach(leftonly);
		if (rightonly != null)
			rightList.stream().filter(e->leftList.stream()
			.noneMatch(t->matchFunction.apply(t, e))).forEach(rightonly);
		if (biConsumer != null)
			leftList.stream()
.map(e->new AbstractMap.SimpleEntry<T, T>(e
	, rightList.stream().filter(t->matchFunction.apply(t, e)).findFirst().orElse(null))
)
			.filter(e->e.getValue() != null).forEach(p->{
				biConsumer.accept(p.getKey(), p.getValue());
			});
	}
}

すると、、

ListDiff<String> listDiff = ListDiff.of((t1, t2)->t1.equals(t2));


listDiff.leftOnly(e->{
	// 後で処理する diff で、左(1番目のリスト)だけに存在する処理
});

listDiff.rightOnly(e->{
	// 後で処理する diff で、右(2番目のリスト)だけに存在する処理
});
listDiff.match((left, right)->{
	// 両方に存在する処理
});

// 比較の実行
listDiff.diff(alist, blist);

リスト要素を等しいと判定する記述が複雑で長くなるような要素のリストを比較する場合、
判定する記述は、1回の記述で済ませられるし、
この ListDiff インスタンスを使い回せることができる。

CSV書込み(2)

CSV書込み、(1) - Oboe吹きプログラマの黙示録

のつづきです。

net.sf.csv4j.CSVWriter を使い、ヘッダ行もデータ行も呼び出し側の与えるデータの問題だと割り切って考えて

以下の関数型インターフェースを用意すれば、とても簡潔になる。

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Collection;
import java.util.function.Supplier;
import net.sf.csv4j.CSVWriter;

/**
 * CSV生成インターフェース.
 */
@FunctionalInterface
public interface CsvCreator extends Serializable{

   public Supplier<Collection<String[]>> getLineElements();

   default public void create(OutputStream out, String charName){
      try(OutputStreamWriter writer = new OutputStreamWriter(out, charName)){
         CSVWriter csvWriter = new CSVWriter(writer);
         for(String[] s:getLineElements().get()){
            csvWriter.writeLine(s);
         }
      }catch(Exception e){
         throw new RuntimeException(e.getMessage(), e);
      }
   }
   default public void createWithDblQuot(OutputStream out, String charName){
      String lineSeparator = System.getProperty("line.separator");
      try(OutputStreamWriter writer = new OutputStreamWriter(out, charName)){
         for(String[] sary:getSupplier().get()){
            for(int i=0;i < sary.length;i++){
               sary[i] = sary[i]==null ? "" : sary[i];
            }
            writer.write(csvline(sary));
            writer.write(lineSeparator);
         }
      }catch(Exception e){
         throw new RuntimeException(e.getMessage(), e);
      }
   }
   default public String csvline(String[] ary){
      return "\"" + Arrays.stream(ary).map(s->s.replaceAll("\"","\"\"")).collect(Collectors.joining("\",\"")) + "\"";
   }
}

CSV出力データになる String[] のリスト

    List<String[]> list;

に対して

    CsvCreator c = ()->()->list;

で宣言して出力実行は、、、

try(OutputStream out = new FileOutputStream("sample.csv")){

   c.create(out, "MS932");

}catch(Exception e){
   e.printStackTrace();
}

で済んでしまう。

ダブルクォートで括る場合は、CsvCreator の createWithDblQuot を使う

   c.createWithDblQuot(out, "UTF-8");


Java 8 が出た時にこういうことは思いつくべきで、今更である。

新しい技術が出たらできるだけ早く新技術を使用する仕事環境に身を置かないと腕が鈍る。。。

CSV書込み、(1)

5年も前に CSVを生成する Javaプログラムコードを、csv4j を使って書いていた。


2011年 3月
http://oboe2uran.hatenablog.com/entry/2011/03/19/101643CSV書き込み(1) - Oboe吹きプログラマの黙示録

http://oboe2uran.hatenablog.com/entry/2011/03/22/210219CSV書き込み(2) - Oboe吹きプログラマの黙示録

http://oboe2uran.hatenablog.com/entry/2011/03/25/121714CSV書き込み(3) - Oboe吹きプログラマの黙示録

でも、1年前、これだと使い勝手が悪くて、 net.sf.csv4j.CSVWriter を使いたくなり、当時、環境としても Java7のままだったので、

次のインターフェースを用意して

public interface Csvoutable{
   /**
    * net.sf.csv4j.CSVWriter#writeLine(String[]) に渡す String[] を生成する。.
    * @return String[]
    */
   public String[] arrays();
}

このインターフェースで受け取った文字列配列をCSV出力する抽象クラスを用意して使った。

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Collection;
import java.util.function.Supplier;
import net.sf.csv4j.CSVWriter;
/**
 * CSV作成抽象クラス.
 */
public abstract class CsvBinary{
   /**
    * ヘッダ行の提供.
    * @return CSVヘッダとなるString配列
    */
   protected abstract String[] getHeaders();

   /**
    * CSVコンテンツ行の提供.
    * @return CSVコンテンツとなる Csvoutable のコレクション
    */
   protected abstract Collection<? extends Csvoutable> getElements();

   /**
    * CSV生成.
    * @param bout 書込み先 OutputStream
    */
   public void write(OutputStream out){
      try{
         OutputStreamWriter writer = new OutputStreamWriter(out, "MS932");
         CSVWriter csvWriter = new CSVWriter(writer);
         csvWriter.writeLine(getHeaders());
         for(Csvoutable c:getElements()){
            String[] a = c.arrays();
            String[] s = new String[a.length];
            for(int i=0;i < s.length;i++){
               s[i] = a[i]==null ? "" : a[i];
            }
            csvWriter.writeLine(s);
         }
         writer.close();
      }catch(Exception e){
         throw new RuntimeException(e.getMessage(), e);
      }
   }
}

使用例は、、

CsvBinary csvbinary = new CsvBinary(){
   @Override
   protected String[] getHeaders(){
      return new String[]{ "A", "B", "C" };
   }
   @Override
      protected Collection<? extends Csvoutable> getElements(){
      return getLineData();
   }
};

のように生成して、getElements で呼ぶgetLineDataは、、

rotected List<Csvoutable> getLineData(){
   List<Csvoutable> csvlist = new ArrayList<Csvoutable>();

   // DB読込み等ハンドラの中で以下を実行

   csvlist.add(new Csvoutable(){
      @Override
      public String[] arrays(){
         return new String[]{
            "11","12","テスト"
         };
      }
   });
   return csvlist;
}

CSVのヘッダ行とデータ行の書込みを明示的で見通しが良いと、1年前は思っていた。
でも、長くて複雑な出力要求がきたら、どうなんだ!?
→ そういう要件自体が問題なんですけど。

Csvoutable に、@FunctionalInterface() を付けたところで
1行書き出し文が、ラムダに代わるくらい、、、

Collection> を返すようメソッドを CsvBinary の中で、、
としても、、

たいして魅力的なコードにはならない。

→ つづきを、CSV書込み(2)で。。。

Wicket 6.x→7.x でファイルダウンロード時のファイル名の注意

Wicket でファイルダウンロードのファイル名を日本語を使用する場合、Wicket 6.x では
URLエンコードしていた。
Wicket 7.x 移行では必要ないことに気がついた。


scheduleRequestHandlerAfterCurrent を使う場合。。

try(IResourceStreamWriter writer = new AbstractResourceStreamWriter(){
   @Override
   public void write(OutputStream out) throws IOException{

      // OutputStream に出力する。

      out.flush();
      out.close();
   }
   @Override
   public String getContentType() {
      // 返す ContentType を指定
      return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
   }
}){
   getRequestCycle().scheduleRequestHandlerAfterCurrent(
     new ResourceStreamRequestHandler(writer, "サンプル.xlsx")
   );
}catch(Exception e){
   // エラー捕捉
}


Wicket 6.x では、

  new ResourceStreamRequestHandler(writer, URLEncoder.encode("サンプル.xlsx", "UTF-8")

と書いていた。。

AjaxBehavior による ダウンロードも以下の抽象クラスを用意することになる

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
import org.apache.wicket.request.resource.ContentDisposition;
import org.apache.wicket.util.resource.IResourceStream;
/**
 * AJAXDownload.
 */
public abstract class AJAXDownload extends AbstractAjaxBehavior{
   protected abstract IResourceStream getResourceStream();

   public void callBackDownload(AjaxRequestTarget target){
      target.appendJavaScript("setTimeout(\"window.location.href='" + getCallbackUrl().toString() + "'\", 100);");
   }
   @Override
   public void onRequest(){
      try{
         ResourceStreamRequestHandler handler = new ResourceStreamRequestHandler(getResourceStream(), getFileName());
         handler.setContentDisposition(ContentDisposition.ATTACHMENT);
         getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent(handler);
      }catch(Exception e){
         throw new RuntimeException(e.getMessage(), e);
      }
   }
   protected String getFileName(){
      return null;
   }
}

使う時、、、

final AJAXDownload download = new AJAXDownload(){
   @Override
   protected IResourceStream getResourceStream(){
      return new AbstractResourceStreamWriter(){
         @Override
         public void write(OutputStream out){
            try{

   // TODO OutputStream に出力

               out.flush();
               out.close();
            }catch(Exception e){
               throw new RuntimeException(e);
            }
         }
         @Override
         public String getContentType() {
            return "application/pdf";
         }
      };
   }
   @Override
   protected String getFileName() {
      return downloadname +".pdf";
   }
};

queue(new AjaxButton("download"){
   @Override
   protected void onSubmit(AjaxRequestTarget target, Form<?> f){
      download.callBackDownload(target);
   }
}.add(download));

Jasperreports のコンパイル実行を、Stream 処理でまとめる

Jasperreports のコンパイル実行を、Stream 処理でまとめてみました。

/**
 * @param directoryPath jrxmlを置いたディレクトリPATH
 * @return Map<String, Throwable> key=コンパイル処理実行のjrxmlファイルPATH、value=コンパイルエラー発生のThroawble
 */
public static Map<String, Throwable> jasperCompileWithDirectory(String directoryPath){
   return  Arrays.stream(new File(directoryPath).listFiles()).filter(e->e.getName().endsWith(".jrxml"))
   .collect(HashMap::new,(r, t)->{
      Throwable error = null;
      try(InputStream in=new FileInputStream(t.getAbsolutePath());
          OutputStream out = new FileOutputStream(t.getAbsolutePath().replaceAll("\\.jrxml", ".jasper"))
      ){
         JasperCompileManager.compileReportToStream(in, out);
      }catch(Exception e){
         error = e;
      }finally{
         r.put(t.getAbsolutePath(), error);
      }
   }, (r, u)->r.putAll(u));
}

使い方は、、、

    Map<String, Throwable> map = jasperCompileWithDirectory(Thread.currentThread().getContextClassLoader()
                                                            .getResource("").getPath() + "../../template");

のようにして、map の value が null ならコンパイル成功