Slick 3.0 多对多查询,连接可迭代
Slick 3.0 many-to-many query with the join as an iterable
我已经使用 Slick 3.0 创建了一个多对多集合,但我很难以我想要的方式检索数据。
事件和兴趣之间存在多对多关系。这是我的表格:
case class EventDao(title: String,
id: Option[Int] = None)
class EventsTable(tag: Tag)
extends Table[EventDao](tag, "events") {
def id = column[Int]("event_id", O.PrimaryKey, O.AutoInc)
def title = column[String]("title")
def * = (
title,
id.?) <> (EventDao.tupled, EventDao.unapply)
def interests = EventInterestQueries.query.filter(_.eventId === id)
.flatMap(_.interestFk)
}
object EventQueries {
lazy val query = TableQuery[EventsTable]
val findById = Compiled { k: Rep[Int] =>
query.filter(_.id === k)
}
}
这是活动兴趣:
case class EventInterestDao(event: Int, interest: Int)
class EventsInterestsTable(tag: Tag)
extends Table[EventInterestDao](tag, "events_interests") {
def eventId = column[Int]("event_id")
def interestId = column[Int]("interest_id")
def * = (
eventId,
interestId) <> (EventInterestDao.tupled, EventInterestDao.unapply)
def eventFk = foreignKey("event_fk", eventId, EventQueries.query)(e => e.id)
def interestFk = foreignKey("interest_fk", interestId, InterestQueries.query)(i => i.id)
}
object EventInterestQueries {
lazy val query = TableQuery[EventsInterestsTable]
}
最后是兴趣:
case class InterestDao(name: String,
id: Option[Int] = None)
class InterestsTable(tag: Tag)
extends Table[InterestDao](tag, "interests") {
def id = column[Int]("interest_id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def name_idx = index("idx_name", name, unique = true)
def * = (
name,
id.?) <> (InterestDao.tupled, InterestDao.unapply)
def events = EventInterestQueries.query.filter(_.interestId === id)
.flatMap(_.eventFk)
}
object InterestQueries {
lazy val query = TableQuery[InterestsTable]
val findById = Compiled { k: Rep[Int] =>
query.filter(_.id === k)
}
}
我可以使用以下查询和检索(event.name,兴趣)的元组:
val eventInterestQuery = for {
event <- EventQueries.query
interest <- event.interests
} yield (event.title, interest.name)
Await.result(db.run(eventInterestQuery.result).map(println), Duration.Inf)
这就是我目前拥有的。
我想要的是能够填充案例 class,例如:
case class EventDao(title: String,
interests: Seq[InterestDao],
id: Option[Int] = None)
问题在于,如果我像这样更新案例 class,它会弄乱我在 EventsTable
中的 def *
投影。另外,我必须将 EventsTable.interests
过滤器重命名为 EventsTable.interestIds
之类的东西,这有点难看,但如果需要我可以接受。
此外,我找不到一种方法来编写生成 (event.name, Seq(interest.name))
的 for
查询。无论如何,这只是我能够产生 (EventDao, Seq(InterestDao))
元组的垫脚石,这正是我真正想要的 return.
有谁知道我怎样才能实现这些目标?我还希望能够 'take' 一定数量的兴趣,因此对于某些查询,所有查询都会 returned,但对于其他查询,只有前 3 个会被
所以在阅读 this page 并在邮件列表上聊天后,我终于让它工作了:
val eventInterestQuery = for {
event <- EventQueries.query
interest <- event.interests
} yield (event, interest)
Await.result(db.run(eventInterestQuery.result
// convert the interests to a sequence.
.map {
_.groupBy(_._1)
.map {
case (k,v) => (k, v.map(_._2))
}.toSeq
}
), Duration.Inf)
groupBy 的唯一问题是您失去了顺序。你可以折叠结果。我已经为我当前的项目编写了这个助手:
def foldOneToMany[A, B](in: Seq[(A, Option[B])], eq: (A, B) => Boolean)
(f: (A, B) => A): Seq[A] =
in.foldLeft(List.empty[A]) {
case (head :: tail, (_, Some(rel))) if eq(head, rel) =>
f(head, rel) :: tail
case (r, (el, Some(rel))) => f(el, rel) :: r
case (r, (el, None)) => el :: r
}.reverse
它可以用一些爱来做。现在它接受一个函数 A,B => 布尔值来确定 B 是否属于 A 和一个函数 A,B => A 将 B 添加到 A.
Virtualeyes 也有一点。在 Postgres 中,您可以使用 array_agg
函数来使用较少的数据库带宽。
我已经使用 Slick 3.0 创建了一个多对多集合,但我很难以我想要的方式检索数据。
事件和兴趣之间存在多对多关系。这是我的表格:
case class EventDao(title: String,
id: Option[Int] = None)
class EventsTable(tag: Tag)
extends Table[EventDao](tag, "events") {
def id = column[Int]("event_id", O.PrimaryKey, O.AutoInc)
def title = column[String]("title")
def * = (
title,
id.?) <> (EventDao.tupled, EventDao.unapply)
def interests = EventInterestQueries.query.filter(_.eventId === id)
.flatMap(_.interestFk)
}
object EventQueries {
lazy val query = TableQuery[EventsTable]
val findById = Compiled { k: Rep[Int] =>
query.filter(_.id === k)
}
}
这是活动兴趣:
case class EventInterestDao(event: Int, interest: Int)
class EventsInterestsTable(tag: Tag)
extends Table[EventInterestDao](tag, "events_interests") {
def eventId = column[Int]("event_id")
def interestId = column[Int]("interest_id")
def * = (
eventId,
interestId) <> (EventInterestDao.tupled, EventInterestDao.unapply)
def eventFk = foreignKey("event_fk", eventId, EventQueries.query)(e => e.id)
def interestFk = foreignKey("interest_fk", interestId, InterestQueries.query)(i => i.id)
}
object EventInterestQueries {
lazy val query = TableQuery[EventsInterestsTable]
}
最后是兴趣:
case class InterestDao(name: String,
id: Option[Int] = None)
class InterestsTable(tag: Tag)
extends Table[InterestDao](tag, "interests") {
def id = column[Int]("interest_id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def name_idx = index("idx_name", name, unique = true)
def * = (
name,
id.?) <> (InterestDao.tupled, InterestDao.unapply)
def events = EventInterestQueries.query.filter(_.interestId === id)
.flatMap(_.eventFk)
}
object InterestQueries {
lazy val query = TableQuery[InterestsTable]
val findById = Compiled { k: Rep[Int] =>
query.filter(_.id === k)
}
}
我可以使用以下查询和检索(event.name,兴趣)的元组:
val eventInterestQuery = for {
event <- EventQueries.query
interest <- event.interests
} yield (event.title, interest.name)
Await.result(db.run(eventInterestQuery.result).map(println), Duration.Inf)
这就是我目前拥有的。
我想要的是能够填充案例 class,例如:
case class EventDao(title: String,
interests: Seq[InterestDao],
id: Option[Int] = None)
问题在于,如果我像这样更新案例 class,它会弄乱我在 EventsTable
中的 def *
投影。另外,我必须将 EventsTable.interests
过滤器重命名为 EventsTable.interestIds
之类的东西,这有点难看,但如果需要我可以接受。
此外,我找不到一种方法来编写生成 (event.name, Seq(interest.name))
的 for
查询。无论如何,这只是我能够产生 (EventDao, Seq(InterestDao))
元组的垫脚石,这正是我真正想要的 return.
有谁知道我怎样才能实现这些目标?我还希望能够 'take' 一定数量的兴趣,因此对于某些查询,所有查询都会 returned,但对于其他查询,只有前 3 个会被
所以在阅读 this page 并在邮件列表上聊天后,我终于让它工作了:
val eventInterestQuery = for {
event <- EventQueries.query
interest <- event.interests
} yield (event, interest)
Await.result(db.run(eventInterestQuery.result
// convert the interests to a sequence.
.map {
_.groupBy(_._1)
.map {
case (k,v) => (k, v.map(_._2))
}.toSeq
}
), Duration.Inf)
groupBy 的唯一问题是您失去了顺序。你可以折叠结果。我已经为我当前的项目编写了这个助手:
def foldOneToMany[A, B](in: Seq[(A, Option[B])], eq: (A, B) => Boolean)
(f: (A, B) => A): Seq[A] =
in.foldLeft(List.empty[A]) {
case (head :: tail, (_, Some(rel))) if eq(head, rel) =>
f(head, rel) :: tail
case (r, (el, Some(rel))) => f(el, rel) :: r
case (r, (el, None)) => el :: r
}.reverse
它可以用一些爱来做。现在它接受一个函数 A,B => 布尔值来确定 B 是否属于 A 和一个函数 A,B => A 将 B 添加到 A.
Virtualeyes 也有一点。在 Postgres 中,您可以使用 array_agg
函数来使用较少的数据库带宽。