如何使用 Scala Slick 编写一对多查询,其中 returns 类似这样的东西`(Model1, Option[Seq[Model2]])`

How to write a One to Many Query with Scala Slick which returns something like this `(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 不会自动为您创建该数据结构。 (我发现从行和表的角度来思考 Slick 以及您可以在便携式 SQL 中做什么而不是从“object-relational 映射器”或类似的角度来思考 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)) ),
     |  ( NewsDataModel(1L), None             ),
     |  ( NewsDataModel(2L), None             ),
     |  ( NewsDataModel(3L), Some(Image(3L))  ),
     | )
     |

val results: Seq[(NewsDataModel, Option[Image])] = List((NewsDataModel(1),Some(Image(1))), (NewsDataModel(1),Some(Image(10))), (NewsDataModel(1),None), (NewsDataModel(2),None), (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),Some(Image(1))), (NewsDataModel(1),Some(Image(10))), (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))), (NewsDataModel(1),List(Image(1), Image(10))), (NewsDataModel(2),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)