Подтвердить что ты не робот

Scala slick query, где в списке

Я пытаюсь научиться использовать Slick для запроса MySQL. У меня есть следующий тип запросов для получения одного объекта Visit:

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

Что я хотел бы знать, так это как я могу изменить приведенный выше запрос на получение списка [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?

4b9b3361

Ответ 1

Как показывает другой ответ, это громоздко связано со статическими запросами. Для статического интерфейса запроса вам необходимо описать параметры привязки как Product. (Int, Int, String*) недействительно scala, а использование (Int,Int,List[String]) также требует некоторых kludges. Кроме того, чтобы гарантировать, что 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

Ответ 2

Это не работает, потому что StaticQuery object (Q) предполагает неявное задание параметров в строке запроса, используя параметры типа метода query для создания своего рода объекта setter (типа scala.slick.jdbc.SetParameter[T]).
Роль SetParameter[T] заключается в установке параметра запроса на значение типа T, где требуемые типы берутся из параметров типа query[...].

Из того, что я вижу, нет такого объекта, определенного для T = List[A] для общего 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


В конце концов, я считаю, что более эффективное обходное решение может быть создано с использованием макросов, чтобы обобщить тип последовательности. Но я не уверен, что это был бы разумный выбор для структуры, учитывая вышеупомянутые проблемы с динамическим характером размера последовательности.

Ответ 3

Вы можете автоматически генерировать предложение таким образом:

  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]

Ответ 4

Если у вас сложный запрос, и для понимания, упомянутого выше, не вариант, вы можете сделать что-то вроде следующего в Slick 3. Но вам нужно убедиться, что вы сами проверяете данные в своем параметре запроса списка, чтобы предотвратить SQL инъекции:

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

# перед ссылкой на переменную отключает проверку типов и позволяет решить эту проблему без предоставления функции для неявного преобразования параметра запроса списка.