Requête Scala slick où dans la liste

J'essaie d'apprendre à utiliser Slick pour interroger MySQL. J'ai le type de requête suivant qui fonctionne 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 je voudrais savoir, c'est Comment puis-je changer ce qui précède en requête pour obtenir une liste[visite] 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
demandé sur ShatyUT 2012-12-28 07:59:37

4 réponses

Comme l'autre réponse le suggère, cela est lourd à faire avec les requêtes statiques. L'interface de requête statique vous oblige à décrire les paramètres de liaison en tant que Product. (Int, Int, String*) n'est pas valide scala, et l'utilisation de (Int,Int,List[String]) nécessite également des kludges. De plus, le fait de devoir s'assurer que locationCodes.size est toujours égal au nombre de (?, ?...) que vous avez dans votre requête est fragile.

En pratique, ce n'est pas trop un problème parce que vous voulez utiliser la monade de requête à la place, qui est le type-safe et moyen recommandé 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 vos tables sont 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
répondu Faiz 2012-12-28 11:42:31

Cela ne fonctionne pas parce que le StaticQuery object (Q) s'attend à définir implicitement les paramètres dans la chaîne de requête, en utilisant les paramètres de type de la méthode query pour créer une sorte d'objet setter (de type scala.slick.jdbc.SetParameter[T]).
Le rôle de SetParameter[T] est de définir un paramètre de requête à une valeur de type T, où les types requis sont tirés des paramètres de type query[...].

D'après ce que je vois, il n'y a pas un tel objet défini pour T = List[A] pour un A générique, et cela semble un choix judicieux, puisque vous ne pouvez pas écrire une requête sql avec une liste dynamique de paramètres pour la clause IN (?, ?, ?,...)


J'ai fait une expérience en fournissant une telle valeur implicite à travers le code suivant

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]

Avec ceci dans la portée, vous devriez être capable d'exécuter votre code

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)

, Mais vous devez toujours manuellement garantir que les locationCodes taille est la même que le nombre de ? votre IN clause


En fin de compte, je crois qu'une solution de contournement plus propre pourrait être créée utilisation de macros, pour généraliser sur le type de séquence. Mais je ne suis pas sûr que ce serait un choix judicieux pour le framework, compte tenu des problèmes susmentionnés avec la nature dynamique de la taille de la séquence.

6
répondu pagoda_5b 2012-12-28 10:32:17

Vous pouvez générer automatiquement dans la 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 setparameter 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
répondu caiiiycuk 2017-05-23 11:54:57

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 ceci dans Slick 3. Mais vous devez vous assurer de valider vous-même les données de votre paramètre de requête list pour éviter L'injection SQL:

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

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

2
répondu markus 2016-10-31 21:32:13