scala - 带有几列的 scala slick 中的动态排序

标签 scala slick

我一直在学习 scala、playframework 和 slick,但我发现了一个问题。
我正在尝试制作一个简单的 CRUD,它带有一个接收自定义过滤器字段的列表 Controller 、一些分页信息(页面大小和数量)以及带有字段名称和顺序(asc 或 desc)的字符串元组 Seq,以及所有内容工作正常,除了按 seq 排序,我无法按动态排序。

我从 Scadiddle blog 得到了基本结构.
所以,基本代码如下:

我有我的基本颜色模型:

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)
      }

在我的 repo 中,我有搜索方法:
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在光滑的文档中:

Be aware that a single ORDER BY with multiple columns is not equivalent to multiple .sortBy calls but to a single .sortBy call passing a tuple



那么,实际上我可以使用 foreach 并连接订单吗?或者是因为这个限制,订单被颠倒了?

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

PD:感谢您的关注,对于糟糕的英语深表歉意

编辑:

感谢您的快速回复,我尝试了您的代码并且看起来不错,遗憾的是我几乎不知道它是如何工作的:((scala 是一种非常好的但很难学习的语言:S)。

当我看到 Higher Kinded Type Should be Enabled我吓坏了,正在寻找答案 this并没有给我太多希望得到一个简单的理解,希望当我完成 Scala 编程,第 3 版书时,我会对到底发生了什么有更多的理解和知识。

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

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

我检查了我的方法并通过在 seq 中添加一个反向使其工作正常,当然不像你的代码那样功能和漂亮,所以我将使用你的建议并工作一种方法来使用助手制作其余的过滤器并避免变量。 (是的,在另一部分中,我仍在使用 var,但是当我对 Scala 了解更多时,我会使它变得更好)。

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

最佳答案

让我们定义 DynamicSortBySupport helper

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 s 和功能:-)
  • 如果你想拥有 ColumnSelector在数据层之外(实际上这是一个很好的做法),您可以重写它,隐式需要类似 ColumnSelector[T] 的内容。 ;
  • 关于scala - 带有几列的 scala slick 中的动态排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42383842/

    相关文章:

    scala - 定义投影以映射到嵌套案例类

    scala - Spark : save and load machine learning model on s3

    scala - 数据帧 : how to groupBy/count then order by count in Scala

    mysql - PlayFramework Slick NoSuchMethodError

    Slick 3 for scala 更新查询不起作用

    scala - RDD API与结合了DataFrame API的UDF对性能的影响

    scala - 序列化具有不可序列化元素的scala case类的最佳方法是什么?

    scala - 如何使用 Scala Slick 表达 Postgres 数组

    mysql - scala/slick 中的哪种数据类型与 mysql 中的地理点匹配?

    java - LWJGL 3.2.0+字体