我坚持使用光滑的查询,但不幸的是找不到类似的例子。
配置:
scalaVersion := "2.11.7"
libraryDependencies += "com.typesafe.play" %% "play-slick" % "2.1.0"
在此情况下。我有一个名为Record
的表/模型。对象本身包含两个序列,即Tags
和Markets
。这是数据库结构的代表图像(我知道这不是ER图,也并非意味着):
Tags
和Markets
拥有自己的表,并通过多对多关系连接到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)
}
我不确定,如果我在正确的道路上。看来这几乎是我想要的。此函数将仅返回带有Records
和Markets
的{{1}},而不是将它们作为可选内容。
我无法解决这个问题。似乎在任何地方都没有这种复杂查询的完整示例。任何帮助是极大的赞赏。预先感谢!
答案 0 :(得分:1)
您的方法正确。假设您的平滑映射定义为:
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])]]
答案 1 :(得分:0)
我终于有时间再次关注这个问题。以我当前的架构和结构,我无法实现@Valerii Rusakov的答案,但是它极大地解决了问题。谢谢!
这就是我的做法:
def myFunction: Future[Seq[(RecordModel, Seq[Option[(TagsModel, Record_TagsModel)]], Seq[Option[(MarketsModel, Record_MarketModel)]], Seq[Option[(UrlsModel, Record_UrlModel)]])]] = {
val query = for {
(((records, tags), markets), urls) <- (records filter (x => x.deleted === false && x.clientId === 1)
joinLeft (tags join recordTags on (_.tagId === _.tagId)) on (_.recordId === _._2.recordId)
joinLeft (markets join recordMarkets on (_.marketId === _.marketId)) on (_._1.recordId === _._2.recordId)
joinLeft (urls join recordUrls on (_.urlId === _.urlId)) on (_._1._1.recordId === _._2.recordId))
} yield (records, tags, markets, urls)
db.run(query.result).map(_.toList.groupBy(_._1).map { // Group by records
case (records, composedResult) =>
(
records,
composedResult.groupBy(_._2).keys.toSeq, // Tags and RecordTags
composedResult.groupBy(_._3).keys.toSeq, // Markets and RecordMarkets
composedResult.groupBy(_._4).keys.toSeq // Urls and RecordUrls
)
}.toSeq)
}
请注意,我屈服于(((records, tags), markets), urls)
。这使我以后可以访问那些精确的属性,从而使分组和映射变得更加容易。它仍然不是完美的,因为我必须使用表和关系表,例如TagsModel, Record_TagsModel
。不过,这只是一个小问题。也许有些人知道如何解决它。当前函数返回所有records
,而与tags
,markets
或urls
无关。