如何用Doobie将SQL结果集转换为Map[ID, List[VALUE]]?
解决方案:将Doobie查询结果转为Map[ID, List[VALUE]]
你的核心需求是把重复ID对应的多个Value聚合为Map[ID, List[VALUE]],原代码的问题在于误用了traverse方法——这个方法用于遍历集合并执行Applicative类型的操作,而你这里只是纯内存中的数据聚合,完全不需要用到它,这也是编译报错的原因((ID, T2)没有默认的Applicative实例)。
正确实现方式
直接利用Scala集合的groupBy和mapValues(或view.mapValues避免中间集合复制)完成聚合:
val query = sql"""select blah""".query[(ID, VALUE)] val result: ConnectionIO[Map[ID, List[VALUE]]] = query.to[List].map { tuples => // 先按ID分组,得到Map[ID, List[(ID, VALUE)]] tuples.groupBy(_._1) // 把每个分组里的元组转换为Value的列表 .view.mapValues(_.map(_._2)) // 转换为不可变Map(如果不需要严格不可变,也可以省略.toMap) .toMap }
代码解释
query.to[List]:从数据库获取所有(ID, VALUE)元组的列表,返回类型是ConnectionIO[List[(ID, VALUE)]]。.map(...):在ConnectionIO的上下文里对结果列表做聚合操作——groupBy(_._1):以元组的第一个元素(ID)为键,把相同ID的元组分到一起。view.mapValues(_.map(_._2)):对每个分组的元组列表,提取出第二个元素(Value)组成新列表;用view可以避免创建中间的Map副本,提升性能。.toMap:将视图转换为标准的不可变Map类型。
备选实现(手动折叠)
如果你想手动控制聚合过程,也可以用foldLeft构建Map:
val result: ConnectionIO[Map[ID, List[VALUE]]] = query.to[List].map { tuples => tuples.foldLeft(Map.empty[ID, List[VALUE]]) { (acc, (id, value)) => acc.updated(id, value :: acc.getOrElse(id, Nil)) }.view.mapValues(_.reverse).toMap }
这种方式需要最后反转列表(因为是头插法),适合需要自定义聚合逻辑的场景。
内容的提问来源于stack exchange,提问作者Paul McKenzie
相关产品推荐
相关产品推荐

