如何从平面 sql select 结果集创建嵌套地图

How to create nested maps from flat sql select result set

SQL 查询类似于:

SELECT DISTINCT REGION, COUNTRY, CITY

生成此结果集

REGION COUNTRY CITY
EUROPE FRANCE  PARIS
EUROPE FRANCE  LYON 
EUROPE FRANCE  NICE
EUROPE GERMANY BERLIN
EUROPE GERMANY DORTMUND 
EUROPE GERMANY HANNOVER

有没有办法使用 google 的 ListMultimap,以便我最终得到一个键 -> 值 -> 值结构?

例如

{EUROPE
    {GERMANY
       {BERLIN, DORTMUND, HANNOVER}, 
     FRANCE
       {PARIS, LYON, NICE}
     }
}

或者另一个包是否更适合这个?

编辑:

尝试实施@Prog_G 的解决方案,但使用以下方法时内存不足。我认为最后我必须将 outer 转换为另一种数据结构。我会以某种方式从中创建地图吗? PS。真实的例子更深一层,但为了问题的缘故想让它更简单。

private static class GeoRowMapper implements RowCallbackHandler {

    ListMultimap<String, ListMultimap<String, ListMultimap<String, String>>> outer = ArrayListMultimap.create();
    ListMultimap<String, ListMultimap<String, String>> middle = ArrayListMultimap.create();
    ListMultimap<String, String> inner = ArrayListMultimap.create();

    @Override
    public void processRow(ResultSet rs) throws SQLException {

        String region = rs.getString("region");
        String country = rs.getString("country");
        String city = rs.getString("city");
        String address = rs.getString("address");
        inner.put(city, address);
        middle.put(country, inner);
        outer.put(region, middle);
    }
    public ListMultimap<String, ListMultimap<String, ListMultimap<String, List<String>>>> get() {
        return Multimaps.asMap(outer);
    }
}

根据 Andy Turner 的评论,您可以使用 Table。在你的情况下,它会是这样的:

ImmutableTable<Region, Country, ImmutableList<City>> immutableTable = RECORDS.stream()
        .collect(toImmutableTable(
                r -> r.getRegion(),
                r -> r.getCountry(),
                r -> ImmutableList.of(r.getCity()),
                (l, l2) -> ImmutableList.<City>builder().addAll(l).addAll(l2).build()
        ));

或者如果你想要 mutable table 作为结果:

Table<Region, Country, List<City>> table = RECORDS.stream()
        .collect(toTable(
                r -> r.getRegion(),
                r -> r.getCountry(),
                r -> Lists.newArrayList(r.getCity()),
                (l, l2) -> {
                    l.addAll(l2);
                    return l;
                },
                HashBasedTable::create
        ));

stream()Collectors.groupingBy 一起工作(致谢 here, see also )。 Set 而不是 List 保证唯一元素。

Map<String, Map<String, Set<String>>> grouped = 
    records.stream()
                .collect(Collectors.groupingBy(r -> r.region,
                         Collectors.groupingBy(r -> r.country,
                         Collectors.mapping(r -> r.city, Collectors.toSet()))));

给出

class Record {
    String city;
    String region;
    String country;
}

目前这实际上会给你 HashMaps 和 HashSet。他们的问题是在迭代条目时缺乏保证顺序。如果结构应该支持用户界面中显示的树,那么您也没有可用的重新排序或 "insert at a index" 操作。例如。 LinkedHashMap and TreeSet 具有可预测的迭代顺序。在创建最终树数据结构时,这种方法仍然可以作为中间步骤。

根据您的喜好,Map.computeIfAbsent 的用法可能是一个选项,它是在 an eye on multimaps 中引入的。

public Map<String, Map<String, Set<String>>> mumap(List<Record> records) {
    Map<String, Map<String, Set<String>>> result = new LinkedHashMap<>();
    for (Record r : records) {
        result.computeIfAbsent(r.region, region -> new LinkedHashMap<>())
              .computeIfAbsent(r.country, country -> new TreeSet<>())
              .add(r.city);
    }
    return result;
}