質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.44%
GROUP BY

GROUP BYとはSQL文のひとつで、SELECT文において特定の列の値が等しい行ごとに表をグループ化します。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

1回答

9456閲覧

javaで複数の変数でgroup byしてリストにする方法について

WalterMontes

総合スコア25

GROUP BY

GROUP BYとはSQL文のひとつで、SELECT文において特定の列の値が等しい行ごとに表をグループ化します。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2021/09/11 07:37

javaで複数の変数でgroup byをして、マージされた数値型のデータをsumし、list型にしてデータを抽出したいです。
短く綺麗に書ける方法があれば教えていただきたいです。

-Item class

Java

1import lombok.AllArgsConstructor; 2import lombok.Data; 3 4@Data 5@AllArgsConstructor 6public class Item { 7 private String orderId; 8 private String itemId; 9 private String itemName; 10 private String itemGenre; 11 private Integer number; 12 13}

以下のデータを用意した時、

java

1final ArrayList<Item> items = new ArrayList<>(); 2 items.add(new Item("00-82-947", "8810", "item name1", "01", 1)); 3 items.add(new Item("00-82-952", "8810", "item name1", "01", 2)); 4 items.add(new Item("00-91-135", "8315", "item name2", "02", 3)); 5 items.add(new Item("00-91-140", "8315", "item name3", "02", 4)); 6 7 // TODO データ抽出処理...(itemId, itemName, itemGenreが同じデータがある場合、numberを合計してマージする) 8 // list型 9 System.out.println(items);

以下の結果になるようにデータを抽出したいです。
orderIdが"00-82-952"と"00-82-947"のデータはitemId, itemName, itemGenreが同じなのでgroup byされ、numberがsumされ3(=1+2)になります。またSQLでgroup byするときと同じように、key(ここではorderId)が小さい方を残したいです。この例だとorderIdが"00-82-952"と"00-82-947"のデータレコードがgroup byされて、"00-82-947"が残ります。

java

1[Item(orderId=00-82-947, itemId=8810, itemName=item name1, itemGenre=01, number=3), 2Item(orderId=00-91-135, itemId=8315, itemName=item name2, itemGenre=02, number=3), 3Item(orderId=00-91-140, itemId=8315, itemName=item name3, itemGenre=02, number=4)]

やりたいことはSQLで表現すると以下になります。

SQL

1select orderId, itemId, itemName, itemGenre, sum(number) as number 2from item 3group by itemId, itemName, itemGenre;

調べたところ以下の方法のようにDtoにequalsとhasCodeをオーバーライドしてgroup byしたい変数を追加すれば実現できますが、
これだとgroup by以外の変数がnullになってしまい(今回だとorderId)、orderIdをsetする処理を書く必要があります。
http://codestudyblog.com/questions/sf/0421195604.html

java

1users.stream() 2.collect(Collectors 3.groupingBy(user -> new User(user.name, user.phone, user.address), 4 Collectors.summarizingLong(user -> user.scope)) 5 ).forEach((k,v) -> { 6 k.scope = v.getSum(); 7 System.out.println(k); 8 });

また以下のようにCollectors.groupingByを使ってMapにすることで実現できそうですが、今回はgroup byの条件が3項目なので、Mapが3つできてしまい、listにするのにコードが長くなってしまいます。
https://stackoverflow.com/questions/28342814/group-by-multiple-field-names-in-java-8

Java

1Map<String, Map<Integer, List<Person>>> map = people 2 .collect(Collectors.groupingBy(Person::getName, 3 Collectors.groupingBy(Person::getAge));

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

Collectorsの利用

ここでやりたいこと。

  • 指定したキーでItemを分類する
  • 分類した複数の Itemの項目を集計する

分類には Collectors#groupingByを一度だけ使う。集計にはダウンストリームCollectorsとしてCollectors#reducingを使う。Itemのインスタンスを合計レコードとして使う。これが構想です。

説明のため、以下のコードはlombokを前提にせず、Itemのフィールドに直接アクセスしています。集計処理で、Itemのインスタンスを毎回作るのはイミュータブルなItemを想定しているから。

Java

1import java.util.TreeMap; 2import java.util.stream.Collectors; 3import java.util.stream.Stream; 4 5public class q358951 { 6 7 public static void main(String[] args) { 8 var list = Stream.of( 9 new Item("00-82-947", "8810", "item name1", "01", 1), 10 new Item("00-82-952", "8810", "item name1", "01", 2), 11 new Item("00-91-135", "8315", "item name2", "02", 3), 12 new Item("00-91-140", "8315", "item name3", "02", 4) 13 ).collect( 14 Collectors.groupingBy( 15 i -> i.itemId + i.itemName + i.itemGenre, // 各項目の文字列の長さが揃っていること 16 TreeMap::new, 17 Collectors.reducing( 18 new Item("99-99-999", "", "", "", 0), // 集計レコードの初期値,集計レコードを別クラスにすることも考えられる 19 (a, i) -> { 20 return new Item( // 集計作業 21 (a.orderId.compareTo(i.orderId) > 0) ? i.orderId : a.orderId, 22 i.itemId, 23 i.itemName, 24 i.itemGenre, 25 a.number + i.number); // 集計値があふれないこと。 26 } 27 ) 28 ) 29 ).values().stream().toList(); 30 list.forEach(System.out::println); 31 } 32}

プログラムが動作するための条件です。

  • 分類キーの各項目の長さが揃っていること(TreeMapを使うため)
  • numberはint型。集計結果が桁溢れしないこと

Tupple

桁溢れする場合は専用の集計レコードを用意する必要があります。タプルが使える言語(Scala,Kotlin)なら、次と同等なタプルをクラスを定義せずに使えます。タプルの_2を集計に使います。

public class Tuple<T,U> { T _1; U _2; public Tuple(T _1, U _2) { this._1 = _1; this._2 = _2; } } Tuple<Item,Long> sum = new Tuple(new Item("99-999-999","","","",0),0l);

Tuppleを使う3引数のCollectors#reducing

Java

1Collectors.reducing( 2 new Tuple<Item,Long>(new Item("99-99-999", "", "", "", 0), 0l), 3 x -> new Tuple<>(x, (long)x.number), 4 (a, i) -> new Tuple<>((a._1.orderId.compareTo(i._1.orderId) > 0) ? i._1: a._1, a._2 + i._2) 5)

SQL の group by

SQL標準では、group by のselect 項目に、グルービング項目と集計関数以外の項目、この場合はorderId、は指定できません。データベース製品の機能拡張になります。MySQLだと、SQL-MODEに標準準拠を指定するとエラーになります。

投稿2021/09/11 10:40

編集2021/09/11 12:17
xebme

総合スコア1087

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xebme

2021/09/11 10:45

Collectors#reducingがあれば、他のCollectros機能は自作できます。引数3つの Collectors#reducingを使ってgroupingByを自作してください。自作することで理解が進みます。
xebme

2021/09/11 12:08

↑これは3引数のStrem#collectの間違い。 Tupleを使う3引数のCollectors#reducingの例が必要なので、追記します。
WalterMontes

2021/09/11 14:29

ご丁寧な解説ありがとうございます。タプルやCollectors.groupingByの部分をしっかり理解するために実際に書いて確認してみます、
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.44%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問