scala - 带有多对多表的光滑嵌套外连接

标签 scala playframework playframework-2.0 slick slick-2.0

我被一个巧妙的查询困住了,不幸的是我找不到类似的例子。

配置:

scalaVersion := "2.11.7"
libraryDependencies += "com.typesafe.play" %% "play-slick" % "2.1.0"

这是场景。我有一个名为 Record 的表/模型.对象本身持有两个序列,即TagsMarkets .这是数据库结构的代表性图像(我知道这不是 ER 图表,它不应该是这样的事实):
enter image description here
TagsMarkets有自己的 table 并连接到Record通过多对多关系。目标是构建一个检索所有记录(不考虑标签和市场)、带有市场的记录和带有标签的记录的查询。我有这样的想法:
Future[Seq[(RecordModel, Option[Seq[MarketModel]], Option[Seq[TagModel]])]]
所以这就是我所拥有的:
def myFunction(): Future[Seq[(RecordModel, Seq[MarketModel], Seq[TagModel])]] = {
  val query = for {
    recs <- records joinLeft (recordsMarkets join markets on (_.marketId === _.marketId)) on (_.recordId === _._1.recordId) joinLeft (recordsTags join tags on (_.tagId === _.tagId)) on (_._1.recordId === _._1.recordId)
  } yield recs
  db.run(query.result).map(_.toList.groupBy(_._1).map {
    case (r, m) => (
      r._1, // Records
      r._2.groupBy(_._2).toSeq.map { case (a, b) => a }, // Markets
      t.flatMap(_._2.groupBy(_._2).map { case (t, relation) => t }) // Tags
    )
  }.toSeq)
}

我不确定,如果我在这里走正确的道路。似乎这几乎是我想要的。这个函数只会返回 RecordsMarketsTags而不是将它们作为可选。

我无法解决这个问题。似乎在任何地方都没有任何此类复杂查询的综合示例。任何帮助是极大的赞赏。提前致谢!

最佳答案

你走对了。假设您的光滑映射定义为:

case class RecordRow(id: Int)

case class TagRow(id: Int)
case class RecordTagRow(recordId: Int, tagId: Int)

case class MarketRow(id: Int)
case class RecordMarketRow(recordId: Int, marketId: Int)

class RecordTable(_tableTag: Tag)
    extends Table[RecordRow](_tableTag, "record") {
  val id = column[Int]("id", O.PrimaryKey, O.AutoInc)

  override def * = id <> ((id: Int) => RecordRow(id), RecordRow.unapply)
}

class TagTable(_tableTag: Tag) extends Table[TagRow](_tableTag, "tag") {
  val id = column[Int]("id", O.PrimaryKey, O.AutoInc)

  override def * = id <> ((id: Int) => TagRow(id), TagRow.unapply)
}

class RecordTagTable(_tableTag: Tag)
    extends Table[RecordTagRow](_tableTag, "record_tag") {
  val recordId = column[Int]("record_id")
  val tagId = column[Int]("tag_id")

  val pk = primaryKey("record_tag_pkey", (recordId, tagId))
  foreignKey("record_tag_record_fk", recordId, RecordQuery)(r => r.id)
  foreignKey("record_tag_tag_fk", tagId, TagQuery)(r => r.id)

  override def * =
    (recordId, tagId) <> (RecordTagRow.tupled, RecordTagRow.unapply)
}

class MarketTable(_tableTag: Tag)
    extends Table[MarketRow](_tableTag, "market") {
  val id = column[Int]("id", O.PrimaryKey, O.AutoInc)

  override def * = id <> ((id: Int) => MarketRow(id), MarketRow.unapply)
}

class RecordMarketTable(_tableTag: Tag)
    extends Table[RecordMarketRow](_tableTag, "record_market") {
  val recordId = column[Int]("record_id")
  val marketId = column[Int]("market_id")

  val pk = primaryKey("record_tag_pkey", (recordId, marketId))
  foreignKey("record_market_record_fk", recordId, RecordQuery)(r => r.id)
  foreignKey("record_market_market_fk", marketId, MarketQuery)(r => r.id)

  override def * =
    (recordId, marketId) <> (RecordMarketRow.tupled, RecordMarketRow.unapply)
}

val RecordQuery = new TableQuery(tag => new RecordTable(tag))
val TagQuery = new TableQuery(tag => new TagTable(tag))
val RecordTagQuery = new TableQuery(tag => new RecordTagTable(tag))
val MarketQuery = new TableQuery(tag => new MarketTable(tag))
val RecordMarketQuery = new TableQuery(tag => new RecordMarketTable(tag))

要连接具有多对多关系的表,您应该以这种方式将左连接与内部连接结合起来:
val recordsQuery = RecordQuery
      .joinLeft(RecordTagQuery.join(TagQuery).on(_.tagId === _.id)).on(_.id === _._1.recordId)
      .joinLeft(RecordMarketQuery.join(MarketQuery).on(_.marketId === _.id)).on(_._1.id === _._1.recordId)

这被 slick 翻译成以下带有 PostgreSQL 配置文件的 SQL:
select
   x2."id",
   x3."id",
   x4."record_id",
   x4."tag_id",
   x3."id",
   x5."id",
   x6."record_id",
   x6."market_id",
   x5."id" 
from
   "record" x2 
   left outer join
      "record_tag" x4 
   inner join
      "tag" x3 
      on x4."tag_id" = x3."id" 
      on x2."id" = x4."record_id" 
   left outer join
      "record_market" x6 
   inner join
      "market" x5 
      on x6."market_id" = x5."id" 
      on x2."id" = x6."record_id"

最后一步是将此查询的结果正确映射到 Scala 类。我是这样做的:
db.run {
  recordsQuery.result
    .map(result => {
      result
        .groupBy(_._1._1) // RecordRow as a key
        .mapValues(values =>values.map(value => (value._1._2.map(_._2), value._2.map(_._2)))) // Seq[(Option[TagRow], Option[MarketRow])] as value
        .map(mapEntry =>(mapEntry._1, mapEntry._2.flatMap(_._1), mapEntry._2.flatMap(_._2)))  // map to Seq[(RecordRow, Seq[TagRow], Seq[MarketRow])]
        .toSeq
    })
}

这将返回 Future[Seq[(RecordRow, Seq[TagRow], Seq[MarketRow])]]

关于scala - 带有多对多表的光滑嵌套外连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53847873/

相关文章:

scala - 带柯里化(Currying)的默认参数

scala - 找到 : org. apache.spark.sql.Dataset[(Double, Double)] 需要 : org. apache.spark.rdd.RDD[(Double, Double)]

scala - CreatePairedStream 不是 MQTTUtils 的成员

java - sbt 如何知道查看 app 文件夹中的 play 框架源代码?

java - 使用 Play! 进行 Web 开发框架

scala - 窗口上的 Spark 条件滞后函数

playframework - 如何在 Play Framework 2.1 中增加堆大小?

google-app-engine - 如何使用 Play Framework 和 Google App Engine 创建 PDF?

java - 使用 Facebook 应用程序访问 token (URL 中的管道字符)

playframework-2.0 - 使用 Mesos 管理 Web App + 数据库集群