読者です 読者をやめる 読者になる 読者になる

白猫のメモ帳

JavaとかJavaScriptとかHTMLとか機械学習とか。

JavaでOracleのPivotみたいなことがしたい

Java

太郎くん、次郎くん、三郎くんはテストの点数を競争していました。

f:id:Shiro-Neko:20140824202605p:plain

しかし、総合点ではなく各教科の点数でそれぞれ順位を決めたくなったため、
集計の方法を変更しました。

f:id:Shiro-Neko:20140824202614p:plain


このとき、このデータをデータベースに登録していたとすると、
いままで主キーが「人」であったのが「人+教科」に変更されることになります。

つまり、3行の情報を1行にまとめるという作業が発生するわけです。
これはOracleでいうPivat関数にあたりそうです。

ただ、OracleのPivotの文法をみればわかりますが、
教科が増えるとSQLを書き直さなければいけないことに気付きます。


めんどうです。


というわけでJava側でできたりしないかなと。

/**
 * ピボット用ユーティリティ
 */
public class PivotUtil {

	/**
	 * 旋回する
	 */
	public static <S, T, U, V extends Pivotable<S, T, U, V>> List<V> pivot(List<V> table) {

		// グループ化する
		Map<S, List<V>> groupMap = table.stream().collect(Collectors.groupingBy(v -> v.groupBy()));

		// 旋回する
		List<V> resList = new ArrayList<V>();
		for (List<V> list : groupMap.values()) {

			// グループ代表的な1件にピボット対象をMap化して設定する
			V pivotable = list.get(0);
			Map<T, U> eleMap = pivotable.getMap();
			for (V p : list) {
				eleMap.put(p.getFor(), p.getValue());
			}

			// 1件だけを追加
			resList.add(pivotable);
		}

		return resList;
	}

	/**
	 * 旋回を解除する
	 */
	public static <S, T, U, V extends Pivotable<S, T, U, V>> List<V> unPivot(List<V> table) {

		List<V> resList = new ArrayList<V>();
		for (V p : table) {

			// グループごとに旋回されている要素をすべて展開する
			for (Entry<T, U> entry : p.getMap().entrySet()) {

				// 行を複製
				V pp = p.copy();

				// 旋回されている要素を展開
				pp.setFor(entry.getKey());
				pp.setValue(entry.getValue());

				// 全件を追加
				resList.add(pp);
			}
		}

		return resList;
	}

	/**
	 * ピボット可能インタフェース
	 */
	public static interface Pivotable<S, T, U, V extends Pivotable<S, T, U, V>> {

		/**
		 * pivotしない部分
		 */
		public S groupBy();

		/**
		 * 集約条件列を取得
		 */
		public T getFor();

		/**
		 * 集約値を取得
		 */
		public U getValue();

		/**
		 * 集約条件列を設定
		 */
		public void setFor(T t);

		/**
		 * 集約値を設定
		 */
		public void setValue(U u);

		/**
		 * pivotされた部分
		 */
		public Map<T, U> getMap();

		/**
		 * 自要素をコピーする
		 */
		public V copy();
	}
}

↑こんなユーティリティを作って

public class TestBo implements Pivotable<String, String, Integer, TestBo> {

	public String userNm = null;

	public String kamokuNm = null;

	public Integer score = 0;

	public Map<String, Integer> scoreMap = new HashMap<String, Integer>();

	public static TestBo of(String userNm, String kamokuNm, Integer score) {
		TestBo bo = new TestBo();
		bo.userNm = userNm;
		bo.kamokuNm = kamokuNm;
		bo.score = score;
		return bo;
	}

	@Override
	public String groupBy() {
		return userNm;
	}

	@Override
	public String getFor() {
		return kamokuNm;
	}

	@Override
	public void setFor(String t) {
		kamokuNm = t;
	}

	@Override
	public Integer getValue() {
		return score;
	}

	@Override
	public void setValue(Integer u) {
		score = u;
	}

	@Override
	public Map<String, Integer> getMap() {
		return scoreMap;
	}

	@Override
	public TestBo copy() {
		return of(userNm, kamokuNm, score);
	}

	public String toString() {
		if (scoreMap.isEmpty()) {
			return userNm + ":" + kamokuNm + "=" + score;
		} else {
			return userNm + ":" + scoreMap.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(","));
		}
	}
}

↑こんなエンティティを

		List<TestBo> list = new ArrayList<TestBo>();

		list.add(TestBo.of("太郎", "国語", 82));
		list.add(TestBo.of("太郎", "数学", 76));
		list.add(TestBo.of("太郎", "英語", 88));

		list.add(TestBo.of("次郎", "国語", 65));
		list.add(TestBo.of("次郎", "数学", 98));
		list.add(TestBo.of("次郎", "英語", 70));

		list.add(TestBo.of("三郎", "国語", 55));
		list.add(TestBo.of("三郎", "数学", 85));
		list.add(TestBo.of("三郎", "英語", 100));

		list.forEach(System.out::println);

		System.out.println();

		list = PivotUtil.pivot(list);

		list.forEach(System.out::println);

↑こんな分に展開するわけです

太郎:国語=82
太郎:数学=76
太郎:英語=88
次郎:国語=65
次郎:数学=98
次郎:英語=70
三郎:国語=55
三郎:数学=85
三郎:英語=100

次郎:国語=65,数学=98,英語=70
三郎:国語=55,数学=85,英語=100
太郎:国語=82,数学=76,英語=88

↑と、結果はこうなる

どうかなぁ。