sql >> Databasteknik >  >> RDS >> Mysql

Slick dynamisk gruppbyte

Här är en lösning för Slick 3.2.3 (och lite bakgrund om mitt tillvägagångssätt):

Du kanske har märkt att du dynamiskt väljer kolumner är lätt så länge du kan anta en fast typ, t.ex.: columnNames = List("col1", "col2") tableQuery.map( r => columnNames.map(name => r.column[String](name)) )

Men om du provar en liknande metod med en groupBy operation kommer Slick att klaga på att den "does not know how to map the given types" .

Så även om detta knappast är en elegant lösning, kan du åtminstone tillfredsställa Slicks typsäkerhet genom att statiskt definiera båda:

  1. groupby kolumntyp
  2. Övre/nedre gräns för mängden groupBy kolumner

Ett enkelt sätt att implementera dessa två begränsningar är att återigen anta en fast typ och att förgrena koden för alla möjliga kvantiteter av groupBy kolumner.

Här är hela den arbetande Scala REPL-sessionen för att ge dig en idé:

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)

Där detta blir fult är när du kan ha uppemot några groupBy kolumner (dvs. har en separat if gren för 10+ fall skulle bli monotont). Förhoppningsvis kommer någon att slå in och redigera det här svaret för hur man gömmer den där plattan bakom något syntaktiskt socker- eller abstraktionslager.




  1. MySQL konverterar YEARWEEK till datum

  2. Mysql hur man går med i tabeller

  3. På RDS kan jag skapa tabeller i en Read Replica som inte finns på Master?

  4. Rails 3 Mysql-problem