29

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

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

我想知道的是如何更改上述内容以查询以获取 List[Visit] 的位置集合......像这样:

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

4 回答 4

31

正如另一个答案所暗示的,这对于静态查询来说很麻烦。静态查询接口要求您将绑定参数描述为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
于 2012-12-28T11:28:25.250 回答
6

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

据我所知,没有T = List[A]为 generic定义这样的对象A,这似乎是一个明智的选择,因为您实际上无法编写带有IN (?, ?, ?,...)子句参数动态列表的 sql 查询


我做了一个实验,通过以下代码提供这样一个隐式值

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


最后,我相信可以使用宏创建更简洁的解决方法,以概括序列类型。但我不确定这是否是框架的明智选择,因为上述序列大小的动态性质存在问题。

于 2012-12-28T10:32:17.870 回答
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)
  }

并使用隐式 SetParameter 如pagoda_5b 所说

  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]
于 2013-03-01T04:18:02.700 回答
2

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

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

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

于 2016-10-31T21:32:13.760 回答