3

我正在尝试将异常查询转换为 Play 2.3 示例之一中的slick ,但我不确定如何实现动态排序。

这是原始方法:

def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%"): Page[(Computer, Option[Company])] = {

    val offest = pageSize * page

    DB.withConnection { implicit connection =>

        val computers = SQL(
        """
          select * from computer 
          left join company on computer.company_id = company.id
          where computer.name like {filter}
          order by {orderBy} nulls last
          limit {pageSize} offset {offset}
        """
        ).on(
            'pageSize -> pageSize,
            'offset -> offest,
            'filter -> filter,
            'orderBy -> orderBy
        ).as(Computer.withCompany *)

        val totalRows = SQL(
        """
          select count(*) from computer 
          left join company on computer.company_id = company.id
          where computer.name like {filter}
        """
        ).on(
            'filter -> filter
        ).as(scalar[Long].single)

        Page(computers, page, offest, totalRows)

    }

}

到目前为止,我已经完成了第一个查询:

val computers_ = (for {
    (computer, company) <- Computer.where(_.name like filter) leftJoin
        Company on (_.companyId === _.id)
} yield (computer, company.?)).list

如何在 slick 中执行“order by”部分,记住它是作为参数动态传递给方法的列名?

Scala 2.10.4 / Play 2.3 / Slick 2.0.2

下面由 Slick 代码生成器生成的表类:

package tables
// AUTO-GENERATED Slick data model
/** Stand-alone Slick data model for immediate use */
object Tables extends {
  val profile = scala.slick.driver.H2Driver
} with Tables

/** Slick data model trait for extension, choice of backend or usage in the cake pattern. (Make sure to initialize this late.) */
trait Tables {
  val profile: scala.slick.driver.JdbcProfile
  import profile.simple._
  import scala.slick.model.ForeignKeyAction
  // NOTE: GetResult mappers for plain SQL are only generated for tables where Slick knows how to map the types of all columns.
  import scala.slick.jdbc.{GetResult => GR}

  /** DDL for all tables. Call .create to execute. */
  lazy val ddl = Company.ddl ++ Computer.ddl

  /** Entity class storing rows of table Company
   *  @param id Database column ID PrimaryKey
   *  @param name Database column NAME  */
  case class CompanyRow(id: Long, name: String)
  /** GetResult implicit for fetching CompanyRow objects using plain SQL queries */
  implicit def GetResultCompanyRow(implicit e0: GR[Long], e1: GR[String]): GR[CompanyRow] = GR{
    prs => import prs._
    CompanyRow.tupled((<<[Long], <<[String]))
  }
  /** Table description of table COMPANY. Objects of this class serve as prototypes for rows in queries. */
  class Company(tag: Tag) extends Table[CompanyRow](tag, "COMPANY") {
    def * = (id, name) <> (CompanyRow.tupled, CompanyRow.unapply)
    /** Maps whole row to an option. Useful for outer joins. */
    def ? = (id.?, name.?).shaped.<>({r=>import r._; _1.map(_=> CompanyRow.tupled((_1.get, _2.get)))}, (_:Any) =>  throw new Exception("Inserting into ? projection not supported."))

    /** Database column ID PrimaryKey */
    val id: Column[Long] = column[Long]("ID", O.PrimaryKey)
    /** Database column NAME  */
    val name: Column[String] = column[String]("NAME")
  }
  /** Collection-like TableQuery object for table Company */
  lazy val Company = new TableQuery(tag => new Company(tag))

  /** Entity class storing rows of table Computer
   *  @param id Database column ID PrimaryKey
   *  @param name Database column NAME 
   *  @param introduced Database column INTRODUCED 
   *  @param discontinued Database column DISCONTINUED 
   *  @param companyId Database column COMPANY_ID  */
  case class ComputerRow(id: Long, name: String, introduced: Option[java.sql.Timestamp], discontinued: Option[java.sql.Timestamp], companyId: Option[Long])
  /** GetResult implicit for fetching ComputerRow objects using plain SQL queries */
  implicit def GetResultComputerRow(implicit e0: GR[Long], e1: GR[String], e2: GR[Option[java.sql.Timestamp]], e3: GR[Option[Long]]): GR[ComputerRow] = GR{
    prs => import prs._
    ComputerRow.tupled((<<[Long], <<[String], <<?[java.sql.Timestamp], <<?[java.sql.Timestamp], <<?[Long]))
  }
  /** Table description of table COMPUTER. Objects of this class serve as prototypes for rows in queries. */
  class Computer(tag: Tag) extends Table[ComputerRow](tag, "COMPUTER") {
    def * = (id, name, introduced, discontinued, companyId) <> (ComputerRow.tupled, ComputerRow.unapply)
    /** Maps whole row to an option. Useful for outer joins. */
    def ? = (id.?, name.?, introduced, discontinued, companyId).shaped.<>({r=>import r._; _1.map(_=> ComputerRow.tupled((_1.get, _2.get, _3, _4, _5)))}, (_:Any) =>  throw new Exception("Inserting into ? projection not supported."))

    /** Database column ID PrimaryKey */
    val id: Column[Long] = column[Long]("ID", O.PrimaryKey)
    /** Database column NAME  */
    val name: Column[String] = column[String]("NAME")
    /** Database column INTRODUCED  */
    val introduced: Column[Option[java.sql.Timestamp]] = column[Option[java.sql.Timestamp]]("INTRODUCED")
    /** Database column DISCONTINUED  */
    val discontinued: Column[Option[java.sql.Timestamp]] = column[Option[java.sql.Timestamp]]("DISCONTINUED")
    /** Database column COMPANY_ID  */
    val companyId: Column[Option[Long]] = column[Option[Long]]("COMPANY_ID")

    /** Foreign key referencing Company (database name FK_COMPUTER_COMPANY_1) */
    lazy val companyFk = foreignKey("FK_COMPUTER_COMPANY_1", companyId, Company)(r => r.id, onUpdate=ForeignKeyAction.Restrict, onDelete=ForeignKeyAction.Restrict)
  }
  /** Collection-like TableQuery object for table Computer */
  lazy val Computer = new TableQuery(tag => new Computer(tag))
}

UPDATE-SOLUTION最终的解决方案就是这个问题

4

3 回答 3

2

我的第一个答案是在正确的位置插入排序功能,但由于 Slick 的复杂输入,它很快变得复杂。您可以通过使用 Slick 的查询组合直接根据所需的顺序修改查询来避免这些键入问题。

def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%") = {
   //..
   val q = for {
     (computer, company) <- Computer.where(_.name like filter) leftJoin
                            Company on (_.companyId === _.id)
   } yield (computer, company.?)

   val sortedQ = orderBy match {
     case 1 => q.sortBy(_._1.id)
     case 2 => q.sortBy(_._1.description)
     // Others
   }

   val pagedQ = sortedQ.drop(page * pageSize).take(pageSize)

   pagedQ.list
}
于 2014-06-12T15:38:41.180 回答
1

Slick 和 Anorm 之间的区别在于 Slick 的查询由 Scala 编译器检查。在 Slick 中实现这样一个动态参数需要付出更多的努力,但作为回报,您会获得类型安全。在这种情况下,这样做特别麻烦,因为您的查询排序是多个表的连接。

一般来说,它应该大致如下所示:

def orderings(code: Int): ((Computer, Company)) => Column[_] = {
   code match {
      case 1 => _._1.id
      case 2 => _._1.description
      // Other orderings
   }
)

def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%") = {
   //..
   val computers_ = (for {
     (computer, company) <- Computer.where(_.name like filter) leftJoin
                            Company on (_.companyId === _.id)
   } yield (computer, company.?))
   .sortBy(orderings(orderBy).nullsLast)
   .drop(page * pageSize)
   .take(pageSize)
   .list
   //..

}

将收到的整数映射到要排序的 Slick 列的一般想法是您问题的答案。

于 2014-06-12T09:56:25.413 回答
1

不确定这是否是世界上最好的主意,但从技术上讲,您可以使用 shapeless 来帮助您获得编号的元组元素,这显然是以编译时安全为代价的。首先使用 Company.unapply 将 Company 案例类转换为元组,然后使用 shapeless 的at(N)方法(注意它是从 0 开始的索引)。这就是它的样子:

def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%") = {
   //..
   val computers_ = (for {
     (computer, company) <- Computer.where(_.name like filter) leftJoin
                            Company on (_.companyId === _.id)
   } yield (computer, company.?))
   .sortBy(Company.unapply(_._1).get.at(orderBy-1).nullsLast)
   .drop(page * pageSize)
   .take(pageSize)
   .list
   //..
}

为了做到这一点,你需要无形:

<dependency>
    <groupId>com.chuusai</groupId>
    <artifactId>shapeless_2.11</artifactId>
    <version>2.3.1</version>
</dependency>

...以及以下导入:

import shapeless.syntax.std.tuple._

使用此技术需要您自担风险。

于 2016-08-16T20:39:59.470 回答