26

在 anorm 中使用“in”子句似乎并不容易:

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

这太复杂了。

有没有更简单的方法?或者游戏应该提供一些使它更容易的东西?

4

7 回答 7

14

Anorm 现在从 2.3 开始支持这种情况(以及更多):“使用多值参数”

回到它给出的初始示例:

val ids = Seq("111", "222", "333")
val users = SQL("select * from users where id in ({ids})").on('ids-> ids).as(parser *)
于 2014-01-22T08:36:47.847 回答
8

搞定了!该线程实际上没有更多更新,但它似乎仍然相关。正因为如此,而且因为没有答案,我想我会把我的考虑进去。

Anorm 不支持“IN”子句。我怀疑他们永远不会。你无法让它们工作,我什至读过一篇文章,其中 anorm 专门删除了这些子句,因为它们让 Anorm 感觉“像一个 ORM”。

但是,将 SqlQuery 包装在一个支持 IN 子句的短类中,然后在需要时将该类转换为 SqlQuery 是相当容易的。

不要在这里粘贴代码,因为它有点长,这里是我博客的链接,我在哪里发布了代码以及如何使用它。

带有异常的子句

基本上,当您获得我博客中的代码时,您的语句如下所示:

RichSQL(""" SELECT * FROM users WHERE id IN ({userIds}) """).onList("userIds" -> userIds).toSQL.as(userParser *)(connection)
于 2013-04-17T22:03:45.967 回答
2

也许为时已晚,但这里有一个使用自定义字符串插值的技巧,它也适用于解决 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:_*)
    }
  }

}
于 2013-11-11T19:28:09.357 回答
1

可能已经晚了,但我为其他寻找相同的人添加了这个。您可以使用一些内置的数据库功能来克服这个问题。这是 Anorm 相对于 ORM 的优势之一。例如,如果您使用的是 PostgreSQL,您可以将列表作为数组传递并在查询中取消嵌套该数组:

我假设 ids 是整数。

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)
于 2013-12-06T17:51:34.930 回答
0

我最近遇到了同样的问题。不幸的是,似乎没有办法不使用字符串插值,因此容易受到 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(","))
于 2012-11-20T23:38:07.877 回答
-1
User.find("id in (%s)"
  .format(params.map("'%s'".format(_)).mkString(",") )
  .list() 
于 2012-07-24T20:19:39.913 回答
-1
val ids = List("111", "222", "333")
val users = SQL("select * from users 
                 where id in 
                 (" +  ids.reduceLeft((acc, s) => acc + "," + s) + ")").as(parser *)
于 2012-07-25T13:01:46.397 回答