R使用dbBind将参数传递给SQL IN子句(可能没有胶合包?)

时间:2018-02-14 01:11:01

标签: r rstudio r-dbi

只是想知道是否可以使用DBI将参数传递给SQL查询IN子句?尝试了以下(以及许多变体,包括未命名的参数)

con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "iris", iris)
iris_result <- dbSendQuery(con, "SELECT * FROM iris WHERE [Petal.Width] > $PW and [Petal.Length] in ($PL)")
dbBind(iris_result, list(PW=2.3, PL={6.0, 5.1}))
dbFetch(iris_result)

此链接Parameterized Queries显示了使用胶水包的方法,但是,我想知道是否可以只使用DBI。

感谢。

注意,作为参考,这里是使用glue的方法:

rs_sql <- glue_sql("SELECT * FROM iris WHERE [Petal.Width] > {pwin} and [Petal.Length] IN ({lengths*})", 
                        pwin = 2.3, lengths = c(6.0, 5.1),
                        .con = con
                        )
iris_result <- dbSendQuery(con, rs_sql)
dbFetch(iris_result)



rs_sql <- glue_sql("SELECT * FROM iris WHERE [Petal.Width] > {pwin} and [Species] IN ({species*})", 
                        pwin = 2.3,
                        species = c('virginica'),
                        .con = con
                        )
iris_result <- dbSendQuery(con, rs_sql)
dbFetch(iris_result)

2 个答案:

答案 0 :(得分:1)

如果您想使用一个参数使用IN在SQL的dbBind()子句中绑定未定义数量的实际值:您可以&#39 ;!吨

library(RSQLite)

con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "iris", iris)

iris_result <- dbSendQuery(con, "SELECT * FROM iris WHERE [Petal.Width] > $PW and [Petal.Length] in ($PL)")
dbBind(iris_result, list(PW=2.3, PL=list(6.0, 5.1)))
# Error in rsqlite_bind_rows(res@ptr, params) : Parameter 2 does not have length 1.

仅当您为IN子句的每个元素定义一个参数时才有效,请参阅SQLite的语法图:

选项1(如果IN元素的数量始终相同):

可能的解决方法是预先定义一些参数,并始终在dbBind中为它们提供值。

con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "iris", iris)

# Works only if you know the number of IN-elements in adavance...
iris_result <- dbSendQuery(con, "SELECT * FROM iris WHERE [Petal.Width] > $PW and [Petal.Length] in ($PL1, $PL2)")
dbBind(iris_result, list(PW=2.3, PL1=6.0, PL2=5.1))
dbFetch(iris_result)
#   Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
# 1          6.3         3.3          6.0         2.5 virginica
# 2          5.8         2.8          5.1         2.4 virginica

选项2(如果IN元素的数量正在改变):

您还可以计算实际参数的数量,并在IN子句中生成相同数量的查询参数,然后使用dbSendQuery准备SQL查询。这可以防止SQL代码注入:

in.params <- c(PL1=6.0, PL2=5.1, PL3=5.6)
sql <- paste0("SELECT * FROM iris WHERE [Petal.Width] > $PW and [Petal.Length] in (",
              paste0("$", names(in.params), collapse = ", "),
              ")")
iris_result <- dbSendQuery(con, sql)
dbBind(iris_result, c(list(PW=2.3), in.params))
dbFetch(iris_result)
#   Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
# 1          6.3         3.3          6.0         2.5 virginica
# 2          5.8         2.8          5.1         2.4 virginica
# 3          6.3         3.4          5.6         2.4 virginica
# 4          6.7         3.1          5.6         2.4 virginica

但这也意味着不重用已准备好的语句,如果这不是您想要的,那么只有SQL语句的经典字符串连接:

选项3:使用SQL字符串连接自行完成:

如果不使用glue包,您只能自己连接SQL字符串,并尝试在(坏)用户输入参数值时尽量减少SQL代码注入的风险。

您可以使用dbQuote*中的DBI函数(RSQLite符合DBI接口)...

答案 1 :(得分:0)

列表中的两个元素必须具有相同的长度。来自?dbBind中的值部分:

  

绑定太多或没有足够的值,或者名称错误或长度不等的参数也会引发错误。如果命名查询中的占位符,则所有参数值必须具有名称(不能为空或NA),反之亦然,否则会引发错误。

进一步在?dbBind中的规范:

  

此列表中的所有元素必须具有相同的长度并包含后端支持的值

以下内容适用于我:

library(RSQLite)
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "iris", iris)
iris_result <- dbSendQuery(con, "SELECT * FROM iris WHERE [Petal.Length] = $PL and [Petal.Width] > $PW")
pl <- c(6.0, 5.1)
dbBind(iris_result, list(PL=pl, PW=rep(2.3, length(pl))))
dbFetch(iris_result)