web-dev-qa-db-ja.com

Scalaリスト内の洗練されたクエリ

MySQLのクエリにSlickを使用する方法を学ぼうとしています。次のタイプのクエリが1つのVisitオブジェクトを取得するために機能しています。

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で可能ですか?

28
ShatyUT

他の回答が示唆しているように、これは静的クエリで行うのは面倒です。静的クエリインターフェイスでは、バインドパラメータをProductとして記述する必要があります。 (Int, Int, String*)は無効なscalaであり、(Int,Int,List[String])を使用するには、いくつかのkludgeも必要です。さらに、locationCodes.sizeが常にクエリ内の(?, ?...)の数と等しいことを確認する必要があることは、脆弱です。

実際には、それほど問題ではありません。代わりにクエリモナドを使用する必要があるためです。これは、タイプセーフであり、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
29
Faiz

_StaticQuery object_(Q)は、queryメソッドの型パラメーターを使用して一種のセッターを作成し、クエリ文字列にパラメーターを暗黙的に設定することを期待しているため、機能しませんオブジェクト(タイプ _scala.slick.jdbc.SetParameter[T]_ )。
_SetParameter[T]_の役割は、クエリパラメータをタイプTの値に設定することです。ここで、必要なタイプは_query[...]_タイプパラメータから取得されます。

一般的なAに対して_T = List[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句の_?_の数と同じであることを常に手動で保証する必要があります


結局のところ、シーケンスタイプを一般化するために、マクロを使用してより明確な回避策を作成できると思います。しかし、シーケンスサイズの動的な性質に関する前述の問題を考えると、フレームワークが賢明な選択になるかどうかはわかりません。

6
pagoda_5b

次のようにして自動的に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]
3
caiiiycuk

複雑なクエリがあり、上記のfor理解がオプションでない場合、Slick 3で次のようなことを行うことができますが、SQLインジェクションを防ぐために、リストクエリパラメータのデータを自分で検証する必要があります。

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

変数参照の前の#は、型の検証を無効にし、リストクエリパラメーターの暗黙的な変換のための関数を提供せずにこれを解決できるようにします。

2
markus