如何使用Scala Slick编写一对多查询,该查询将返回类似“Model1,Option [Seq [Model2]]”的内容

问题描述

我知道之前曾有人问过这个问题,但我无法弄清楚。 在我的数据模型中,我有一个新闻模型,其中包含任意数量的图像。

case class NewsDataModel(
                                newsId: Option[Long],name: String,description: String,author: String,creationDateTime: Option[OffsetDateTime]
                        )

case class Image(
                        id: Long,url: String,newsId: Long
                )

现在,我想查询数据库获取类似(NewsDataModel,Option[Seq[Image]])

的信息

我的查询当前按以下方式实现:

val q = newsTable.joinLeft(imagesTable).on(_.newsId === _.newsId).result
db.run(q)

计算结果为Future[Seq[(NewsDataModel,Option[Image])]]。我猜想解决此问题的正确方法是使用groupBy函数,但自此以来我不知道如何实现


val q = newsTable.joinLeft(imagesTable).on(_.newsId === _.newsId)
       .groupBy(_._1.newsId)
        .result

db.run(q)

评估为Future[Seq[(Option[Long],Query[(NewsTable,Rep[Option[ImagesTable]]),(NewsDataModel,Option[Image]),Seq])]]

解决方法

Slick不会自动为您创建该数据结构。 (我发现从行和表以及在可移植SQL中可以做什么而不是从“对象关系映射器”或类似的角度来考虑Slick很有帮助。)

您要做的是在数据库层之后将行转换为所需的Scala格式。有很多方法可以做到这一点。

这是做到这一点的一种方法。

给出此示例数据...

scala> case class NewsDataModel(newsId: Long)
class NewsDataModel

scala> case class Image(id: Long)
class Image

scala> val results = Seq(
     |  ( NewsDataModel(1L),Some(Image(1L))  ),|  ( NewsDataModel(1L),Some(Image(10L)) ),None             ),|  ( NewsDataModel(2L),|  ( NewsDataModel(3L),Some(Image(3L))  ),| )
     |

val results: Seq[(NewsDataModel,Option[Image])] = List((NewsDataModel(1),Some(Image(1))),(NewsDataModel(1),Some(Image(10))),None),(NewsDataModel(2),(NewsDataModel(3),Some(Image(3))))

我们可以按键分组:

scala> val groups = results.groupBy { case (key,values) => key }

val groups: scala.collection.immutable.Map[NewsDataModel,Seq[(NewsDataModel,Option[Image])]] = HashMap(NewsDataModel(3) -> List((NewsDataModel(3),Some(Image(3)))),NewsDataModel(1) -> List((NewsDataModel(1),None)),NewsDataModel(2) -> List((NewsDataModel(2),None)))

并将其转换为所需的类型:

scala> val flat = groups.map { case (key,seq) => key -> seq.flatMap(_._2) }

val flat: scala.collection.immutable.Map[NewsDataModel,Seq[Image]] = HashMap(NewsDataModel(3) -> List(Image(3)),NewsDataModel(1) -> List(Image(1),Image(10)),NewsDataModel(2) -> List())

flat结果是一张地图,但您可以将其变成(例如)一个列表,该列表具有您想要的类型签名(接近):

scala> flat.toList

val res18: List[(NewsDataModel,Seq[Image])] = List((NewsDataModel(3),List(Image(3))),List(Image(1),Image(10))),List()))

您可以找到许多方法来做到这一点,但重点是您是在Scala中进行操作,而不是Slick(SQL)。请特别注意,我使用的groupBy方法是集合库中的Scala方法,而不是Slick方法(这将是SQL GROUP BY子句)。也就是说,我正在修改运行查询的结果,而不是修改查询本身。

我建议将您想要的任何转换放入方法中,然后将其应用于Slick操作。例如:

def convert(input: Seq[(NewsDataModel,Option[Image])]): Seq[(NewsDataModel,Seq[Image])] =
   ??? // your implementation here

val action = newsTable.joinLeft(imagesTable).on(_.newsId === _.newsId).result
val convertedAction = action.map(convert)
db.run(convertedAction)