

こんにちは。

業務でJavaのインターフェースを設計しているのですが、  
どのような処理にすればよいかわからない点があり、ご質問させていただきます。  
設計しているのはテーブルからレコードを取得してきて、出力編集して画面に返却するようなインターフェースになります。  
java8、db2を使用しています。

○処理概要について  
下記のような点数テーブルがあるとして

名前 | 年月 | 学期 | 国語 | 数学 | 英語  
---|---|---|---|---|---  
山田 | 202204 | 1 | 60 | 43 | 72  
山田 | 202205 | 1 | 63 | 52 | 68  
山田 | 202206 | 1 | 64 | 56 | 64  
山田 | 202209 | 2 | 63 | 53 | 72  
山田 | 202210 | 2 | 67 | 59 | 64  
山田 | 202211 | 2 | 59 | 62 | 63  
~ | ~ | ~ | ~ | ~ | ~

[code]

    select 学期, avg(国語), avg(数学), avg(英語) from 点数 where 名前='山田' group by 年月
    
[/code]  
  
というsqlで返ってきた値をこのようなマップのリスト型に格納します。

[code]

    result=[{学期:xx,国語:□□,数学:△△,英語:☆☆},{〃},.]
    
[/code]

現在は学期別に集計されていますが、  
画面では下記のようなテーブル形式で表示させたいです。  
画面に出力するためにインターフェースから返す値も、マップのリスト型になります。

[code]

    [{科目:国語,1学期:○○,2学期:××,３学期:△△},{〃},...]
    
[/code]

山田さんの点数：

科目 | 1学期 | 2学期 | 3学期  
---|---|---|---  
国語 | 62.3 | 63 | ○○  
数学 | 50.3 | 58 | ××  
英語 | 68 | 66 | △△  
  
行と列が入れ替わるようなイメージでしょうか。  
制約として、科目別に集計するテーブルの追加は不可能で、現状の点数テーブルの形式から取得してくる必要があります。  
ですので、sqlかjavaのロジックで行列の入れ替えを実現する必要があると考えています。

sqlの場合は下のようなものを科目分unionすれば実現できそうですが、unionの回数がとても多くなり、パフォーマンスの面で不安があります。  
(実際は科目にあたる項目が100個ほどあります…)

[code]

    select '国語' as 科目
           max(CASE WHEN 学期 = '1' THEN 国語 END) AS 1学期,
           max(CASE WHEN 学期 = '2' THEN 国語 END) AS 2学期,
           max(CASE WHEN 学期 = '3' THEN 国語 END) AS 3学期
    from 点数 
    where 名前='山田'
    group by 学期;
    
[/code]

javaで実現する場合は、できればループ処理で出力させたいのですが、  
恥ずかしながら処理の方針が浮かんでおりません…

何か良いやり方があればご教示いただけますでしょうか。


CC BY-SA 4.0
2022-12-05T06:31:17.717
92587
2022-12-07T02:40:21.950
2022-12-05T06:50:42.650
3054
55781
question
java sql アルゴリズム db2
Javaでテーブルでの集計方法と画面に表示する集計方法が異なる場合の処理


キーが2次元(科目, 学期)であるならば、その組で引けるようなデータ構造にすれば良いかと考えます。

以下の例はMapのMap `{学期: {科目: 平均値}}` で実装した例です。  
この例のデータ構造では、後述の通り、 `averages.get(t).get(s)` という 学期`t` と 科目`s`
を引数にとって平均値を返すインタフェースを提供していることになります。

DBから取得した直後:

[code]

            var queryResultList = List.of(
                    Map.of("学期", 1, "国語", 62.33, "数学", 50.33, "英語", 68.0),
                    Map.of("学期", 2, "国語", 63.0, "数学", 58.0, "英語", 66.33)
            );
    
[/code]

`{学期: {科目: 平均値}}` となるようなMapに変換:

[code]

            var averages = queryResultList.stream().collect(Collectors.toMap(
                    e -> {
                        var termValue = e.get("学期").intValue();
                        return Term.from(termValue);
                    },
                    e -> {
                        var qrMap = new HashMap<>(e);
                        qrMap.remove("学期");
    
                        return qrMap.entrySet().stream().collect(Collectors.toMap(
                                f -> Subject.from(f.getKey()),
                                f -> f.getValue().doubleValue()
                        ));
                    }
            ));
    
[/code]

出力例:

[code]

            for (Subject s : Subject.values()) {
                for (Term t : Term.values()) {
                    var avg = averages.get(t).get(s);
                    System.out.printf("%.2f\t", avg);
                }
                System.out.println();
            }
    
[/code]

科目ループと学期ループの内外を入れ替えれば転置します:

[code]

            for (Term t : Term.values()) {
                for (Subject s : Subject.values()) {
                    var avg = averages.get(t).get(s);
                    System.out.printf("%.2f\t", avg);
                }
                System.out.println();
            }
    
[/code]

[サンプル実装](https://github.com/yukihane/stackoverflow-qa/tree/main/jaso92587)


CC BY-SA 4.0
2022-12-06T21:48:04.250
92608
2022-12-07T02:40:21.950
2022-12-07T02:40:21.950
2808
2808
92587
answer