«В» предложение в норме? - PullRequest
26 голосов
/ 02 марта 2012

Кажется, нет простого способа использовать предложение in in anorm:

val ids = List("111", "222", "333")
val users = SQL("select * from users where id in ({ids})").on('ids-> ???).as(parser *)

Как заменить ??? деталь?

Я пытался:

on('ids -> ids)
on('ids -> ids.mkString("'","','","'"))
on('ids -> ids.mkString("','")

Но ничего не работает.

Я вижу в обсуждении точно такую ​​же проблему: https://groups.google.com/d/topic/play-framework/qls6dhhdayc/discussion, у автора есть комплексное решение:

val params = List(1, 2, 3) 

val paramsList = for ( i <- 0 until params.size ) yield ("userId" + i) 

// ---> results in List("userId0", "userId1", "userId2") 

User.find("id in ({%s})"

    // produces "id in ({userId0},{userId1},{userId2})"
    .format(paramsList.mkString("},{")) 

    // produces Map("userId0" -> 1, "userId1" -> 2, ...) 
    .on(paramsList.zip(params))
    .list() 

Это слишком сложно.

Есть ли более простой способ?Или игра должна дать что-нибудь, чтобы было легче?

Ответы [ 7 ]

14 голосов
/ 22 января 2014

Anorm теперь поддерживает такой случай (и более), начиная с 2.3: "Использование многозначного параметра"

Вернемся к исходному примеру:

val ids = Seq("111", "222", "333")
val users = SQL("select * from users where id in ({ids})").on('ids-> ids).as(parser *)
8 голосов
/ 18 апреля 2013

прибил его!В этой теме больше не было обновлений, но, похоже, все еще актуально.Из-за этого и из-за отсутствия ответа я подумал, что я должен добавить свой для рассмотрения.

Anorm не поддерживает предложения IN.Я сомневаюсь, что они когда-либо будут.Вы ничего не можете сделать, чтобы заставить их работать, я даже читал пост, где anorm специально исключил эти пункты, потому что они заставили Anorm чувствовать себя «как ORM».

Однако довольно легко обернуть SqlQuery вкороткий класс, который поддерживает предложение IN, а затем конвертирует этот класс в SqlQuery, когда это необходимо.

Вместо вставки кода здесь, потому что он становится немного длиннее, вот ссылка на мой блог, гдеЯ разместил код и как его использовать.

В предложении с Anorm

В основном, когда у вас есть код из моего блога, ваши заявления выглядят так:

RichSQL(""" SELECT * FROM users WHERE id IN ({userIds}) """).onList("userIds" -> userIds).toSQL.as(userParser *)(connection)
2 голосов
/ 11 ноября 2013

Возможно, уже слишком поздно, но вот совет по использованию пользовательской интерполяции строк, который также помогает решить проблему предложения IN.

Я реализовал вспомогательный класс для определения интерполяции строк. Вы можете увидеть это ниже, и вы можете просто скопировать и вставить, но сначала давайте посмотрим, как вы можете его использовать.

Вместо того, чтобы что-то написать

SQL("select * from car where brand = {brand} and color = {color} and year = {year} order by name").on("brand" -> brand, "color" -> color, "year" -> year).as(Car.simple *)

Вы можете просто написать:

SQL"select * from car where brand = $brand and color = $color and year = $year order by name".as(Car.simple *)

Таким образом, используя интерполяцию строк, она более краткая и удобная для чтения.

А для случая использования предложения IN вы можете написать:

val carIds = List(1, 3, 5)
SQLin"select * from car where id in ($carIds)".as(Car.simple *)

Или для вашего примера:

val ids = List("111", "222", "333")
val users = SQLin"select * from users where id in ($ids)".as(parser *)

Для получения дополнительной информации о интерполяции строк, проверьте эту ссылку

Код для этого неявного класса следующий:

package utils

object AnormHelpers {

  def wild (str: String) = "%" + str + "%"

  implicit class AnormHelper (val sc: StringContext) extends AnyVal {

    // SQL raw -> it simply create an anorm.Sql using string interpolation
    def SQLr (args: Any*) = {
      // Matches every argument to an arbitrary name -> ("p0", value0), ("p1", value1), ...
      val params = args.zipWithIndex.map(p => ("p"+p._2, p._1))
      // Regenerates the original query substituting each argument by its name with the brackets -> "select * from user where id = {p0}"
      val query = (sc.parts zip params).map{ case (s, p) => s + "{"+p._1+"}" }.mkString("") + sc.parts.last
      // Creates the anorm.Sql
      anorm.SQL(query).on( params.map(p => (p._1, anorm.toParameterValue(p._2))) :_*)
    }

    // SQL -> similar to SQLr but trimming any string value
    def SQL (args: Any*) = {
      val params = args.zipWithIndex.map {
        case (arg: String, index) => ("p"+index, arg.trim.replaceAll("\\s{2,}", " "))
        case (arg, index) => ("p"+index, arg)
      } 
      val query = (sc.parts zip params).map { case (s, p) => s + "{"+ p._1 + "}" }.mkString("") + sc.parts.last
      anorm.SQL(query).on( params.map(p => (p._1, anorm.toParameterValue(p._2))) :_*)
    }

    // SQL in clause -> similar to SQL but expanding Seq[Any] values separated by commas
    def SQLin (args: Any*) = {
      // Matches every argument to an arbitrary name -> ("p0", value0), ("p1", value1), ...
      val params = args.zipWithIndex.map {
        case (arg: String, index) => ("p"+index, arg.trim.replaceAll("\\s{2,}", " "))
        case (arg, index) => ("p"+index, arg)
      }
      // Expands the Seq[Any] values with their names -> ("p0", v0), ("p1_0", v1_item0), ("p1_1", v1_item1), ...
      val onParams = params.flatMap {
        case (name, values: Seq[Any]) => values.zipWithIndex.map(v => (name+"_"+v._2, anorm.toParameterValue(v._1)))
        case (name, value) => List((name, anorm.toParameterValue(value)))
      }
      // Regenerates the original query substituting each argument by its name expanding Seq[Any] values separated by commas
      val query = (sc.parts zip params).map {
        case (s, (name, values: Seq[Any])) => s + values.indices.map(name+"_"+_).mkString("{", "},{", "}")
        case (s, (name, value)) => s + "{"+name+"}"
      }.mkString("") + sc.parts.last
      // Creates the anorm.Sql
      anorm.SQL(query).on(onParams:_*)
    }
  }

}
1 голос
/ 06 декабря 2013

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

Я предполагаю, что идентификаторы целые.

val ids = List(1, 2, 3)

val idsPgArray = "{%s}".format(ids.mkString(",")) //Outputs {1, 2, 3}

val users = SQL(
  """select * from users where id in (select unnest({idsPgArray}::integer[]))"""
).on('ids-> ???).as(parser *)

Выполненный запрос будет

select * from users where id in (select unnest('{1, 2, 3}'::integer[])) 

, что равно

select * from users where id in (1, 2, 3)
0 голосов
/ 21 ноября 2012

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

То, что я в итоге сделал, было своего рода санация, преобразовав его в список целых и обратно:

val input = "1,2,3,4,5"

// here there will be an exception if someone is trying to sql-inject you
val list = (_ids.split(",") map Integer.parseInt).toList

// re-create the "in" string
SQL("select * from foo where foo.id in (%s)" format list.mkString(","))
0 голосов
/ 25 июля 2012
val ids = List("111", "222", "333")
val users = SQL("select * from users 
                 where id in 
                 (" +  ids.reduceLeft((acc, s) => acc + "," + s) + ")").as(parser *)
0 голосов
/ 25 июля 2012
User.find("id in (%s)"
  .format(params.map("'%s'".format(_)).mkString(",") )
  .list() 
...