Berikut adalah solusi untuk Slick 3.2.3 (dan beberapa latar belakang pendekatan saya):
Anda mungkin telah memperhatikan secara dinamis memilih kolom mudah selama Anda dapat mengasumsikan tipe tetap, misalnya:
columnNames = List("col1", "col2")
tableQuery.map( r => columnNames.map(name => r.column[String](name)) )
Namun jika Anda mencoba pendekatan serupa
dengan groupBy
operasi, Slick akan mengeluh bahwa "does not know how to map the given types"
.
Jadi, meskipun ini bukanlah solusi yang elegan, Anda setidaknya dapat memenuhi keamanan tipe Slick dengan mendefinisikan keduanya secara statis:
groupby
jenis kolom- Batas atas/bawah pada jumlah
groupBy
kolom
Cara sederhana untuk mengimplementasikan kedua batasan ini adalah dengan mengasumsikan kembali tipe tetap dan membuat cabang kode untuk semua jumlah groupBy
yang mungkin. kolom.
Inilah sesi REPL Scala yang berfungsi untuk memberi Anda gambaran:
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)
Di mana ini menjadi jelek adalah ketika Anda dapat memiliki lebih dari beberapa groupBy
kolom (yaitu memiliki if
separate yang terpisah cabang untuk 10+ kasus akan menjadi monoton). Semoga seseorang akan masuk dan mengedit jawaban ini untuk cara menyembunyikan boilerplate itu di balik beberapa gula sintaksis atau lapisan abstraksi.