我有这样的代码:
def query(buckets: List[String]): Future[Seq[(List[Option[String]], Option[Double])]] = {
database.run {
groupBy(row => buckets.map(bucket => customBucketer(row.metadata, bucket)))
.map { grouping =>
val bucket = grouping._1
val group = grouping._2
(bucket, group.map(_.value).avg)
}
.result
}
}
private def customBucketer(metadata: Rep[Option[String]], bucket: String): Rep[Option[String]] = {
...
}
我希望能够在 slick which groupby 中创建查询并收集给定的列列表。
我在编译时遇到的错误是:
[error] Slick does not know how to map the given types.
[error] Possible causes: T in Table[T] does not match your * projection,
[error] you use an unsupported type in a Query (e.g. scala List),
[error] or you forgot to import a driver api into scope.
[error] Required level: slick.lifted.FlatShapeLevel
[error] Source type: List[slick.lifted.Rep[Option[String]]]
[error] Unpacked type: T
[error] Packed type: G
[error] groupBy(row => buckets.map(bucket => customBucketer(row.metadata, bucket)))
[error] ^
最佳答案
这是 Slick 3.2.3 的解决方法(以及我的方法的一些背景):
您可能已经注意到动态选择列很容易,只要您可以采用固定类型,例如:
columnNames = List("col1", "col2")
tableQuery.map( r => columnNames.map(name => r.column[String](name)) )
但是如果你try a similar approach使用 groupBy
操作,Slick 会提示它“不知道如何映射给定类型”
。
因此,虽然这不是一个优雅的解决方案,但您至少可以通过静态定义两者来满足 Slick 的类型安全:
groupby
列类型groupBy
列数量的上限/下限
实现这两个约束的一个简单方法是再次假设一个固定类型并为所有可能数量的 groupBy
列分支代码。
下面是完整的 Scala REPL session ,可以给你一个想法:
import java.io.File
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import slick.jdbc.H2Profile.api._
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
val confPath = getClass.getResource("/application.conf")
val config = ConfigFactory.parseFile(new File(confPath.getPath)).resolve()
val db = Database.forConfig("slick.db", config)
implicit val system = ActorSystem("testSystem")
implicit val executionContext = system.dispatcher
case class AnyData(a: String, b: String)
case class GroupByFields(a: Option[String], b: Option[String])
class AnyTable(tag: Tag) extends Table[AnyData](tag, "macro"){
def a = column[String]("a")
def b = column[String]("b")
def * = (a, b) <> ((AnyData.apply _).tupled, AnyData.unapply)
}
val table = TableQuery[AnyTable]
def groupByDynamically(groupBys: Seq[String]): DBIO[Seq[GroupByFields]] = {
// ensures columns are returned in the right order
def selectGroups(g: Map[String, Rep[Option[String]]]) = {
(g.getOrElse("a", Rep.None[String]), g.getOrElse("b", Rep.None[String])).mapTo[GroupByFields]
}
val grouped = if (groupBys.lengthCompare(2) == 0) {
table
.groupBy( cols => (cols.column[String](groupBys(0)), cols.column[String](groupBys(1))) )
.map{ case (groups, _) => selectGroups(Map(groupBys(0) -> Rep.Some(groups._1), groupBys(1) -> Rep.Some(groups._2))) }
}
else {
// there should always be at least one group by specified
table
.groupBy(cols => cols.column[String](groupBys.head))
.map{ case (groups, _) => selectGroups(Map(groupBys.head -> Rep.Some(groups))) }
}
grouped.result
}
val actions = for {
_ <- table.schema.create
_ <- table.map(a => (a.column[String]("a"), a.column[String]("b"))) += ("a1", "b1")
_ <- table.map(a => (a.column[String]("a"), a.column[String]("b"))) += ("a2", "b2")
_ <- table.map(a => (a.column[String]("a"), a.column[String]("b"))) += ("a2", "b3")
queryResult <- groupByDynamically(Seq("b", "a"))
} yield queryResult
val result: Future[Seq[GroupByFields]] = db.run(actions.transactionally)
result.foreach(println)
Await.ready(result, Duration.Inf)
当您可以拥有多个 groupBy
列时(即,为 10 个以上的情况使用单独的 if
分支会变得单调)。希望有人会突然介入并编辑此答案,以了解如何将样板文件隐藏在某些语法糖或抽象层后面。
关于mysql - 光滑的动态分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49699392/