web-dev-qa-db-fra.com

Scala requête slick où dans la liste

Je tente d'apprendre à utiliser Slick pour interroger MySQL. J'ai le type de requête suivant qui travaille pour obtenir un seul objet de visite:

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

Ce que j'aimerais savoir, c'est comment puis-je modifier ce qui précède en question pour obtenir une liste [visiter] pour une collection d'emplacements ... quelque chose comme ceci:

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)

Est-ce possible avec Slick?

28
ShatyUT

Au fur et à mesure que l'autre réponse suggère, cela est lourde à faire avec des requêtes statiques. L'interface de requête statique nécessite de décrire les paramètres de liaison comme un Product. (Int, Int, String*) n'est pas valide Scala et en utilisant (Int,Int,List[String]) nécessite également des karges. En outre, devoir s'assurer que locationCodes.size est toujours égal au nombre de (?, ?...) Vous avez dans votre requête est fragile.

En pratique, ce n'est pas trop problématique, car vous souhaitez utiliser la requête monade à la place, qui est la solution de type-coffre-fort et recommandée d'utiliser 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

Cela suppose que vous avez vos tables configurées comme ceci:

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

Notez que vous pouvez toujours envelopper votre requête dans une méthode.

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

Vous pouvez générer automatiquement en clause comme ceci:

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

Et utilisez un paramètre implicite comme pagoda_5b dit

  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

Si vous avez une requête complexe et que la compréhension mentionnée ci-dessus n'est pas une option, vous pouvez faire quelque chose comme ce qui suit dans SLICK 3. Mais vous devez vous assurer de valider les données de votre paramètre de requête de la liste vous-même pour empêcher l'injection SQL:

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

Le # devant la référence variable désactive la validation de type et vous permet de résoudre ce problème sans fournir une fonction pour la conversion implicite du paramètre de requête de liste.

2
markus