sql >> Database >  >> RDS >> Mysql

Gladde dynamische groupby

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:

  1. groupby kolomtype
  2. 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.




  1. Foreign Key SQL:alles wat u moet weten over Foreign Key Operations

  2. De invoer uit het HTML-formulier halen en opslaan in mysql via Flask

  3. Is het mogelijk om een ​​MySQL-opgeslagen procedure aan te roepen vanuit Ruby?

  4. Op afstand toegang krijgen tot MySQL-server via SSH-tunnel