按多个变量按最短值分组并求和 Java

Group by multiple variables by shortest value and sum in Java

我想按多个变量分组并与数字相加,并在 java 中得到列表的结果。像SQL group by,想合并最低位字符串的数据记录。 我要做的和下面的SQL一样,

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

如果数据存在于下面的table项中,

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

我预计结果会低于此。当按 00-82-947 和 00-82-952 对 orderId 进行分组时,我想获得较低的分组,例如 SQL group by.

00-82-947, 8810, item name1, 01, 3, 
00-91-135, 8315, item name2, 02, 3, 
00-91-140, 8315, item name3, 02, 4

如何在 Java 中实现它?我认为这对我有用,但在这种情况下,未分组的 orderId 将为空,因此我需要创建一个新的 class 来填充 orderId。 http://codestudyblog.com/questions/sf/0421195604.html

这也行,但我想要列表的结果。所以我需要将它隐藏起来映射三次,因为我需要按三次分组。 Group by multiple field names in java 8

所以我正在寻找一种可能使用 java 流的更好方法。 作为参考,我留下代码。

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Item {
    private String orderId;
    private String itemId;
    private String itemName;
    private String itemGenre;
    private Integer number;

}

准备数据

   final ArrayList<Item> items = new ArrayList<>();
   items.add(new Item("00-82-947", "8810", "item name1", "01", 1));
   items.add(new Item("00-82-952", "8810", "item name1", "01", 2));
   items.add(new Item("00-91-135", "8315", "item name2", "02", 3));
   items.add(new Item("00-91-140", "8315", "item name3", "02", 4));
   
   System.out.println(items);

我希望打印结果在下面。

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

SQL 查询似乎缺少应用于 orderId 的聚合函数 MIN:

SELECT MIN(orderId), itemId, itemName, itemGenre, SUM(number) as number
FROM item
GROUP BY itemId, itemName, itemGenre;

要使用具有合并功能的 Stream API Collectors.toMap 实现类似功能,应在合并功能选择 orderId 的最小值并求和 number 的地方使用。使用 LinkedHashMap 来维护插入顺序可能也更好。

此外,复制构造函数应该在 Item class 中实现,或者在选择要放置到中间映射的值时从 items 列表中克隆项目。

然后这张图的值被转换成ArrayList.

List<Item> summary = new ArrayList<>(items
        .stream()
        .collect(Collectors.toMap(
            // compound "group by" key using fields for brevity
            i -> String.join("|", i.itemId, i.itemName, i.itemGenre),
            i -> i.clone(), // or Item::new if copy constructor is implemented
                            // or verbose i -> new Item(i.orderId, i.itemId, ...)
            (i1, i2) -> {
                if (i1.orderId.compareToIgnoreCase(i2.orderId) < 0) {
                    i1.setOrderId(i2.orderId);
                }
                i1.setNumber(i1.number + i2.number);
                return i1;
            },
            LinkedHashMap::new
        ),
        )
        .values() // Collection<Item>
);

或者,可以在合并函数中创建一个新对象:

List<Item> summary = new ArrayList<>(items
        .stream()
        .collect(Collectors.toMap(
            // compound "group by" key using fields for brevity
            i -> String.join("|", i.itemId, i.itemName, i.itemGenre),
            i -> i, // or Function.identity()
            (i1, i2) -> new Item( // merge function
                i1.orderId.compareToIgnoreCase(i2.orderId) <= 0 ? i1.orderId : i2.orderId,
                i1.itemId, i1.itemName, i1.itemGenre, // "group by" fields
                i1.number + i2.number
            ),
            LinkedHashMap::new
        ))
        .values() // Collection<Item>
);

我喜欢保持我的流代码简短且易于概述,即使有时这意味着必须在幕后隐藏更多代码才能使事情正常进行。所以我的出发点是:

    List<Item> items = List.of(
            new Item("00-82-947", "8810", "item name1", "01", 1),
            new Item("00-82-952", "8810", "item name1", "01", 2),
            new Item("00-91-135", "8315", "item name2", "02", 3),
            new Item("00-91-140", "8315", "item name3", "02", 4));
    
    Map<GroupByKey, List<Item>> lists = items.stream()
            .collect(Collectors.groupingBy(Item::getGroupByKey));
    Map<GroupByKey, SumForOrder> grouped = lists.entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> new SumForOrder(e.getValue())));
    
    grouped.forEach((k, v) -> System.out.println("" + k + " -> " + v));

输出:

8810 item name1 01 -> 00-82-947  3
8315 item name3 02 -> 00-91-140  4
8315 item name2 02 -> 00-91-135  3

我首先执行常规 groupingBy 操作,将您的项目分类到每个组的列表中。为此,我创建了一个包含 itemIditemNameitemGenreGroupByKey class,以及您的 Item 的一个方法 getGroupByKey class 构造一个 GroupByKey 对象。

public GroupByKey getGroupByKey() {
    return new GroupByKey(itemId, itemName, itemGenre);
}

接下来,我将列表映射转换为包含我为此目的创建的另一个 class SumForOrder 的对象的映射。 SumForOrder 的构造函数完成了大部分实际工作,从项目列表中找到最小值 orderId 并对数字求和:

public class SumForOrder {

    private String orderId;
    private int sum;

    public SumForOrder(Collection<Item> itemsForOrder) {
        orderId = itemsForOrder.stream()
                .map(Item::getOrderId)
                .min(Comparator.naturalOrder())
                .orElseThrow();
        sum = itemsForOrder.stream()
                .map(Item::getNumber)
                .filter(Objects::nonNull)
                .mapToInt(Integer::intValue)
                .sum();
    }

    @Override
    public String toString() {
        return String.format("%-9s %2d", orderId, sum);
    }

}

您也可以简单地创建新的 Item 个对象,而不是 SumForOrder 个对象。在这种情况下,您不需要 SumForOrder class.

您需要将 min() 聚合函数应用于 orderid,如下所示:

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

然后试试这个。

static String min(String a, String b) { return a.compareTo(b) <= 0 ? a : b; }

public static void main(String[] args) {

    record Item(String orderId, String itemId, String itemName, String itemGenre, Integer number) {}
    List<Item> items = List.of(
        new Item("00-82-947", "8810", "item name1", "01", 1),
        new Item("00-82-952", "8810", "item name1", "01", 2),
        new Item("00-91-135", "8315", "item name2", "02", 3),
        new Item("00-91-140", "8315", "item name3", "02", 4));

    record ItemKey(String itemId, String itemName, String itemGenre) {}
    record ItemValue(String orderId, Integer number) {}

    Map<ItemKey, ItemValue> map = items.stream()
        .collect(Collectors.toMap(
            e -> new ItemKey(e.itemId(), e.itemName(), e.itemGenre()),
            e -> new ItemValue(e.orderId(), e.number()),
            (a, b) -> new ItemValue(min(a.orderId(), b.orderId()), a.number() + b.number()),
            LinkedHashMap::new));
            
    for (Entry<ItemKey, ItemValue> e : map.entrySet())
        System.out.println(e);
}

输出:

ItemKey[itemId=8810, itemName=item name1, itemGenre=01]=ItemValue[orderId=00-82-947, number=3]
ItemKey[itemId=8315, itemName=item name2, itemGenre=02]=ItemValue[orderId=00-91-135, number=3]
ItemKey[itemId=8315, itemName=item name3, itemGenre=02]=ItemValue[orderId=00-91-140, number=4]