Mysql
 sql >> Base de données >  >> RDS >> Mysql

Grouper dynamique et élégant

Voici une solution de contournement pour Slick 3.2.3 (et quelques informations sur mon approche) :

Vous avez peut-être remarqué la sélection dynamique les colonnes sont faciles tant que vous pouvez supposer un type fixe, par exemple : columnNames = List("col1", "col2") tableQuery.map( r => columnNames.map(name => r.column[String](name)) )

Mais si vous essayez une approche similaire avec un groupBy opération, Slick se plaindra qu'il "does not know how to map the given types" .

Ainsi, bien que ce ne soit pas une solution élégante, vous pouvez au moins satisfaire la sécurité de type de Slick en définissant statiquement les deux :

  1. groupby type de colonne
  2. Limite supérieure/inférieure de la quantité de groupBy colonnes

Une façon simple d'implémenter ces deux contraintes est de supposer à nouveau un type fixe et de brancher le code pour toutes les quantités possibles de groupBy colonnes.

Voici la session de travail complète de Scala REPL pour vous donner une idée :

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)

Là où cela devient moche, c'est quand vous pouvez avoir plus de quelques groupBy colonnes (c'est-à-dire avoir un if séparé branche pour plus de 10 cas deviendrait monotone). J'espère que quelqu'un interviendra et modifiera cette réponse pour savoir comment masquer ce passe-partout derrière un sucre syntaxique ou une couche d'abstraction.