Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Geschicktes dynamisches Groupby

Hier ist eine Problemumgehung für Slick 3.2.3 (und einige Hintergrundinformationen zu meinem Ansatz):

Sie haben vielleicht bemerkt, dass dynamisch ausgewählt wird Spalten ist einfach, solange Sie einen festen Typ annehmen können, z. (Name)) )

Aber wenn Sie einen ähnlichen Ansatz versuchen mit einem groupBy Operation, wird sich Slick darüber beschweren, dass es "nicht weiß, wie man die angegebenen Typen abbildet" .

Obwohl dies keine elegante Lösung ist, können Sie zumindest die Typsicherheit von Slick erfüllen, indem Sie beide statisch definieren:

  1. gruppieren nach Spaltentyp
  2. Obere/untere Grenze der Menge von groupBy Spalten

Eine einfache Möglichkeit, diese beiden Einschränkungen zu implementieren, besteht darin, wieder einen festen Typ anzunehmen und den Code für alle möglichen Mengen von groupBy zu verzweigen Spalten.

Hier ist die voll funktionsfähige Scala REPL-Sitzung, um Ihnen eine Vorstellung zu geben:

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)

Wo dies hässlich wird, wenn Sie mehr als ein paar groupBy haben können Spalten (d.h. mit einem separaten if Verzweigung für 10+ Fälle würde eintönig werden). Hoffentlich kommt jemand herein und bearbeitet diese Antwort, um zu erfahren, wie man diese Boilerplate hinter einer syntaktischen Zucker- oder Abstraktionsschicht verbirgt.