在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()
这太复杂了。
有没有更简单的方法?或者应该提供一些让事情变得更容易的东西?
答案 0 :(得分:14)
自从2.3:"Using multi-value parameter"
以来,Anorm现在支持这种情况(以及更多)回到最初的例子,它给出了:
val ids = Seq("111", "222", "333")
val users = SQL("select * from users where id in ({ids})").on('ids-> ids).as(parser *)
答案 1 :(得分:8)
钉了它!这个帖子还没有真正的更新,但似乎仍然有用。因此,并且因为没有答案,我以为我会把我扔进去考虑。
Anorm不支持'IN'条款。我怀疑他们会不会。你无法做任何事情让他们工作,我甚至读了一篇文章,其中anorm专门删除了这些条款,因为他们让Anorm觉得'就像一个ORM'。
然而,将SqlQuery包装在支持IN子句的短类中,然后在需要时将该类转换为SqlQuery相当容易。
不要在这里粘贴代码,因为它有点长,这里是我的博客的链接,我发布了代码以及如何使用它。
基本上,当你从我的博客获得代码时,你的陈述如下:
RichSQL(""" SELECT * FROM users WHERE id IN ({userIds}) """).onList("userIds" -> userIds).toSQL.as(userParser *)(connection)
答案 2 :(得分: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 *)
有关字符串插值的更多信息,请查看此link
此隐式类的代码如下:
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:_*)
}
}
}
答案 3 :(得分: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)
答案 4 :(得分: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(","))
答案 5 :(得分:-1)
User.find("id in (%s)"
.format(params.map("'%s'".format(_)).mkString(",") )
.list()
答案 6 :(得分:-1)
val ids = List("111", "222", "333")
val users = SQL("select * from users
where id in
(" + ids.reduceLeft((acc, s) => acc + "," + s) + ")").as(parser *)