Hier is een oplossing voor Slick 3.2.3 (en wat achtergrondinformatie over mijn aanpak):
Het is je misschien opgevallen dat je dynamisch selecteert kolommen is eenvoudig zolang u een vast type kunt aannemen, bijvoorbeeld:
columnNames = List("col1", "col2")
tableQuery.map( r => columnNames.map(name => r.column[String](name)) )
Maar als je een vergelijkbare aanpak probeert
met een groupBy
operatie, zal Slick klagen dat het "does not know how to map the given types"
.
Dus hoewel dit nauwelijks een elegante oplossing is, kunt u op zijn minst voldoen aan de typeveiligheid van Slick door beide statisch te definiëren:
groupby
kolomtype- Boven-/ondergrens van het aantal
groupBy
kolommen
Een eenvoudige manier om deze twee beperkingen te implementeren is om opnieuw een vast type aan te nemen en de code te vertakken voor alle mogelijke hoeveelheden groupBy
kolommen.
Hier is de volledig werkende Scala REPL-sessie om je een idee te geven:
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)
Waar dit lelijk wordt, is wanneer je meer dan een paar groupBy
. kunt hebben kolommen (d.w.z. met een aparte if
branch voor 10+ gevallen zou eentonig worden). Hopelijk duikt er iemand in en bewerkt dit antwoord voor het verbergen van die standaardtekst achter een syntactische suiker- of abstractielaag.