Scala中具有多个列的动态ORDER BY多个、动态、Scala、ORDER

2023-09-03 13:39:40 作者:冷心人 △

我一直在学习Scala、Play框架和Slick,但我发现了一个问题。 我正在尝试创建一个简单的CRUD,它带有一个列表控制器,它接收一个定制的筛选字段、一些分页信息(页面大小和编号)以及一个字符串元组序列,其中包含字段名和顺序(asc或desc),所有操作都运行良好,除了按顺序排序外,我不能按动态排序。

我从Scadiddle blog获得了基本结构。 因此,基本代码如下:

SQLite全面学习 二

我有我的基本颜色模型:

case class Color(
  id: Int,
  name: String)

这是一个简单的表定义:

    class ColorsTable(tag: Tag) extends Table[Color](tag, "color") {
        def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
        def name = column[String]("name")
        def * = (id, name) <> ((Color.apply _).tupled, Color.unapply)
      }

在我的回购中,我有搜索方法:

def findAll(searchTerm: Option[String], page: Int, top: Int, sortsBy: Seq[(String, SortDirection)]): Future[Seq[Color]] = {
    var query = searchTerm match {
      case Some(term) => colors.filter(_.name like s"%$term%")
      case _ => colors
    }
    val offset = (page - 1) * top
    // This is building the sort clause, matching on each column in the sort
    sortsBy.foreach {
      sortTuple =>
        val (sortColumn, sortDirection) = sortTuple
        query = query.sortBy(sortColumn match {
          case "id" => if (sortDirection == Desc) _.id.desc else _.id.asc
          case _ => if (sortDirection == Desc) _.name.desc else _.name.asc
        })
    }
    // The "list" method actually executes the query
    val colorsQuery = query.drop(offset).take(top).result

    db.run(colorsQuery)
  }

问题是,当我使用以下序列调用搜索方法时:

val sortsBy = Seq[(String, SortDirection)](("name", Desc), ("id", Asc))
    colorService.getColors(None, 1, 10, sortsBy).map(colorList => Ok(Json.toJson(colorList)))

生成此查询:

select "id", "name" from "color" order by "id", "name" desc limit 10 offset 0

如您所见,sortBy的顺序颠倒了(顺序是id,然后是name,而不是name和id)。

如果我使用元组而不是Foreach,则遵循顺序:

query = query.sortBy(
      s => (s.name.desc, s.id.asc)
    )

但无法生成动态大小元组。更令人困惑的是,另一件给我带来麻烦的事情是this part在流畅的文档中:

请注意,具有多列的单个ORDER BY不是 等同于多个.sortBy调用,但对应于单个.sortBy调用 传递元组

那么,在现实中,我可以使用Foreach并连接订单吗?或者是因为这个限制,顺序颠倒了?

如果只能将元组用于sortBy,如何实现动态大小的排序依据?

PD:谢谢您的关注,很抱歉英语不好

编辑:

谢谢你的快速响应,我试过你的代码,看起来很好,遗憾的是我几乎不知道它是如何工作的:((Scala是一种非常好的语言,但很难学:s)。

当我看到Higher Kinded Type Should be Enabled时,我吓坏了,寻找答案this并没有给我带来多少轻松理解的希望,希望当我完成Scala第三版的编程时,我会对地狱发生的事情有更多的理解和知识。

还有一个问题,这是否等同于执行多个sortBy调用?这与使用元组相比如何?我仍然对这部分圆滑的文档感到困惑:

具有多个列的单个ORDER BY不等同于多个.sortBy调用,而等同于传递元组的单个.sortBy调用

我检查了我的方法,通过向seq添加一个反向函数,使它工作得很好,当然不像功能那样好,也不像您的代码那样好,所以我将采纳您的建议,并设法使用帮助器和避免var来创建其余的筛选器。(是的,在另一部分中,我仍然使用var,但当我对Scala有了更多的了解后,我会做得更好)。

自白:在用几种语言(从Java到Java、C#、Python等)编程8年多之后,我不得不重复一遍,Scala看起来是一门美丽但非常复杂的语言,但我不会放弃学习它

推荐答案

让我们定义DynamicSortBySupport帮助器

object DynamicSortBySupport {
  import slick.ast.Ordering.Direction
  import slick.ast.Ordering
  import slick.lifted.Query
  import slick.lifted.ColumnOrdered
  import slick.lifted.Ordered
  type ColumnOrdering = (String, Direction) //Just a type alias
  trait ColumnSelector {
    val select: Map[String, Rep[_]] //The runtime map between string names and table columns 
  }
  implicit class MultiSortableQuery[A <: ColumnSelector, B, C[_]](query: Query[A, B, C]) {
    def dynamicSortBy(sortBy: Seq[ColumnOrdering]): Query[A, B, C]  =
      sortBy.foldRight(query){ //Fold right is reversing order
        case ((sortColumn, sortOrder), queryToSort) =>
          val sortOrderRep: Rep[_] => Ordered = ColumnOrdered(_, Ordering(sortOrder))
          val sortColumnRep: A => Rep[_] = _.select(sortColumn)
          queryToSort.sortBy(sortColumnRep)(sortOrderRep)
      }
  }
}

并重新定义Table添加"排序映射"

class ColorsTable(tag: Tag) extends Table[Color](tag, "color") with DynamicSortBySupport.ColumnSelector {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def * = (id, name) <> ((Color.apply _).tupled, Color.unapply)
  val select = Map(
    "id" -> (this.id),
    "name" -> (this.name)
  )
}

并最终在您的代码中使用整个代码:

object FindAll extends App {
  import DynamicSortBySupport._
  import slick.ast.Ordering.Direction
  import slick.ast.Ordering
  object colors extends TableQuery(new ColorsTable(_))
  val sortsBy = Seq[(String, Direction)](("name", Ordering.Desc), ("id", Ordering.Asc)) //Replaced 
  val db = Database.forURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver="org.h2.Driver") //Just for testing
  findAll(sortsBy)
  def findAll(sortsBy: Seq[(String, Direction)]): Future[Seq[Color]] = {
    val query = colors.dynamicSortBy(sortsBy).result
    db.run(query)
  }
}

备注和评论:

字符串名称和表列之间的运行时映射Map[String, Rep[_]]可以通过错误处理(现在它只是抛出必须正确管理的运行时异常)或从表定义本身自动派生来改进; 我已将SortDirection替换为合适的slick.ast.Ordering.Direction,请随意编写转换器; 您还可以为可选过滤器编写帮助器,类似于filterOption; 注意foldRight用于颠倒排序顺序; 如果可能,避免var并使用:-) 如果您想将ColumnSelector放在数据层之外(实际上这是一个很好的做法),您可以重写它,隐式地要求类似ColumnSelector[T]