Scala slick consulta donde en la lista


Estoy intentando aprender a usar Slick para consultar MySQL. Tengo el siguiente tipo de consulta trabajando para obtener un único objeto Visit:

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

Lo que me gustaría saber es cómo puedo cambiar lo anterior a query para obtener una Lista[Visita] para una colección de Ubicaciones...algo como esto:

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)

¿Es esto posible con Slick?

Author: ShatyUT, 2012-12-28

4 answers

Como sugiere la otra respuesta, esto es engorroso para hacer consultas estáticas. La interfaz de consulta estática requiere que describa los parámetros de enlace como Product. (Int, Int, String*) no es scala válido, y usar (Int,Int,List[String]) también necesita algunos kludges. Además, para asegurarse de que locationCodes.size es siempre igual al número de (?, ?...) que tiene en su consulta es frágil.

En la práctica, esto no es demasiado un problema porque desea utilizar la mónada de consulta en su lugar, que es el tipo seguro y forma recomendada de usar 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

Esto es asumiendo que tienes tus tablas configuradas de esta manera:

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

Tenga en cuenta que siempre puede envolver su consulta en un método.

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
Author: Faiz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-28 11:42:31

No funciona porque el StaticQuery object (Q) espera establecer implícitamente los parámetros en la cadena de consulta, utilizando los parámetros de tipo del método query para crear una especie de objeto setter (de tipo scala.slick.jdbc.SetParameter[T]).
La función de SetParameter[T] es establecer un parámetro de consulta a un valor de tipo T, donde los tipos requeridos se toman de los parámetros de tipo query[...].

Por lo que veo no hay tal objeto definido para T = List[A] para un genérico A, y parece una elección sensata, ya que en realidad, no se puede escribir una consulta sql con una lista dinámica de parámetros para la cláusula IN (?, ?, ?,...)


Hice un experimento proporcionando tal valor implícito a través del siguiente código

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]

Con esto en el alcance, debería ser capaz de ejecutar su código

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)

Pero siempre debe garantizar manualmente que el tamaño locationCodes es el mismo que el número de ? en su cláusula IN


Al final creo que se podría crear una solución más limpia usando macros, para generalizar en el tipo de secuencia. Pero no estoy seguro de que sería una buena elección para el marco, dados los problemas mencionados con la naturaleza dinámica del tamaño de la secuencia.

 6
Author: pagoda_5b,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-28 10:32:17

Puedes generar en la cláusula automáticamente así:

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

Y use setParameter implícito como pagoda_5b dice

  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
Author: caiiiycuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:57

Si tiene una consulta compleja y la opción para la comprensión mencionada anteriormente no es una opción, puede hacer algo como lo siguiente en Slick 3. Pero debe asegurarse de validar los datos en el parámetro de consulta de la lista usted mismo para evitar la inyección SQL:

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

El # delante de la referencia de la variable desactiva la validación de tipo y le permite resolver esto sin proporcionar una función para la conversión implícita del parámetro de consulta de lista.

 2
Author: markus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-31 21:32:13