Scala光滑查询列表中的位置

时间:2012-12-28 03:59:37

标签: scala typesafe-stack slick

我正在尝试学习使用Slick来查询MySQL。我有以下类型的查询来获取单个Visit对象:

Q.query[(Int,Int), Visit]("""
    select * from visit where vistor = ? and location_code = ?
""").firstOption(visitorId,locationCode)

我想知道的是如何更改以上内容以查询以获取位置集合的列表[访问] ...类似这样:

val locationCodes = List("loc1","loc2","loc3"...)
Q.query[(Int,Int,List[String]), Visit]("""
    select * from visit where vistor = ? and location_code in (?,?,?...)
""").list(visitorId,locationCodes)

Slick可以实现吗?

4 个答案:

答案 0 :(得分:29)

正如另一个答案所示,这对于静态查询来说很麻烦。静态查询接口要求您将绑定参数描述为Product(Int, Int, String*) 是无效的scala,使用(Int,Int,List[String])也需要一些kludges。此外,必须确保locationCodes.size始终等于查询中(?, ?...)的数量,这很脆弱。

在实践中,这不是太大的问题,因为您希望使用查询monad,这是使用Slick的类型安全和推荐的方式。

val visitorId: Int = // whatever
val locationCodes = List("loc1","loc2","loc3"...)
// your query, with bind params.
val q = for {
    v <- Visits 
    if v.visitor is visitorId.bind
    if v.location_code inSetBind locationCodes
  } yield v
// have a look at the generated query.
println(q.selectStatement)
// run the query
q.list

这假设你的表设置如下:

case class Visitor(visitor: Int, ... location_code: String)

object Visitors extends Table[Visitor]("visitor") {
  def visitor = column[Int]("visitor")
  def location_code = column[String]("location_code")
  // .. etc
  def * = visitor ~ .. ~ location_code <> (Visitor, Visitor.unapply _)
}

请注意,您始终可以将查询包装在方法中。

def byIdAndLocations(visitorId: Int, locationCodes: List[String]) = 
  for {
    v <- Visits 
    if v.visitor is visitorId.bind
    if v.location_code inSetBind locationCodes
  } yield v
}

byIdAndLocations(visitorId, List("loc1", "loc2", ..)) list

答案 1 :(得分:6)

它不起作用,因为StaticQuery objectQ)期望隐式设置查询字符串中的参数,使用query方法的类型参数创建一种setter对象(类型scala.slick.jdbc.SetParameter[T])。
SetParameter[T]的作用是将查询参数设置为类型T的值,其中所需类型取自query[...]类型参数。

从我看到的T = List[A]没有为通用A定义这样的对象,它似乎是一个明智的选择,因为你实际上不能用动态的参数列表编写一个sql查询对于IN (?, ?, ?,...)子句


我通过以下代码

提供了这样一个隐含值来做实验
import scala.slick.jdbc.{SetParameter, StaticQuery => Q}

def seqParam[A](implicit pconv: SetParameter[A]): SetParameter[Seq[A]] = SetParameter {  
    case (seq, pp) =>
        for (a <- seq) {
            pconv.apply(a, pp)
        }
}

implicit val listSP: SetParameter[List[String]] = seqParam[String]

在范围内,你应该能够执行你的代码

val locationCodes = List("loc1","loc2","loc3"...)
Q.query[(Int,Int,List[String]), Visit]("""
    select * from visit where vistor = ? and location_code in (?,?,?...)
""").list(visitorId,locationCodes)

但您必须始终手动保证locationCodes大小与?子句中IN的数量相同


最后,我相信可以使用宏创建更清晰的解决方法,以概括序列类型。但鉴于上述问题与序列大小的动态性质有关,我不确定它是否是框架的明智选择。

答案 2 :(得分:3)

您可以像这样自动生成in子句:

  def find(id: List[Long])(implicit options: QueryOptions) = {
    val in = ("?," * id.size).dropRight(1)
    Q.query[List[Long], FullCard](s"""
        select 
            o.id, o.name 
        from 
            organization o
        where
            o.id in ($in)
        limit
            ?
        offset
            ?
            """).list(id ::: options.limits)
  }

并使用隐式GetParameter作为pagoda_5b says

  def seqParam[A](implicit pconv: SetParameter[A]): SetParameter[Seq[A]] = SetParameter {
    case (seq, pp) =>
      for (a <- seq) {
        pconv.apply(a, pp)
      }
  }

  implicit def setLongList = seqParam[Long]

答案 3 :(得分:2)

如果您有一个复杂的查询并且上面提到的for comprehension不是一个选项,您可以在Slick 3中执行类似下面的操作。但是您需要确保自己验证列表查询参数中的数据以防止SQL注射:

val locationCodes = "'" + List("loc1","loc2","loc3").mkString("','") + "'"
sql"""
  select * from visit where visitor = $visitor 
    and location_code in (#$locationCodes)
"""

变量引用前面的#禁用类型验证,并允许您在不提供列表查询参数的隐式转换函数的情况下解决此问题。