【发布时间】:2019-12-09 11:53:29
【问题描述】:
我正在处理 SQL 和用户输入。所以我使用glue 库来处理参数化查询。
但是,为了保持整洁,我将它们全部封装在一个函数中:
safeQuery <- function(con, sql, ...) {
sql = glue_sql(sql, ..., .con=con)
query <- dbSendQuery(con, sql)
out <- dbFetch(query)
dbClearResult(query)
return(out)
}
所以我只是用glue_sql 适当绑定的SQL 代码的连接、SQL 代码和参数列表调用该函数。
这很好用。
现在,我有一个特定的 SQL 调用,我经常以一种或另一种方式使用它,但参数不同。
所以我决定为此创建一个函数:
get_data <- function(con, params) {
safeQuery(con,
"SELECT *
FROM foo
WHERE bar IN ({vars*})",
vars=params)
}
p = c(1, 2)
get_data(con, p)
因此,用户数据(在本例中为 c(1, 2))将被传递给 get_data,后者会将其与 SQL 调用一起传递给 safeQuery,glue_sql 将负责绑定。
但是,如果我真的尝试运行 get_data,我会收到一个错误
object 'params' not found
谷歌搜索和 SO'ing 已经清楚地表明这与 R 的惰性评估有关。
确实,将get_data 更改为
get_data <- function(con, params) {
do.call("safeQuery",
list(con,
"SELECT *
FROM foo
WHERE bar IN ({vars*})",
vars=params)
}
(由this answer 推荐)工作得很好,因为do.call 在将列表中的参数发送到safeQuery 之前评估它们。
我不明白为什么这首先是必要的。毕竟,params 的值在到达glue_sql 的过程中的任何一步都没有被修改,所以它应该仍然可用。
链接的答案讨论了使用substitute(我还阅读了有关该主题的this R-bloggers post)将参数的名称替换为调用者的名称(或者如果直接给出参数值,则使用其实际值),但这对我来说不起作用。修改get_data使用substitute:
get_data <- function(con, params) {
do.call("safeQuery",
list(con,
"SELECT *
FROM foo
WHERE bar IN ({vars*})",
vars=substitute(params))
}
导致来自glue_sql的以下SQL:
SELECT *
FROM foo
WHERE bar IN (params)
而不是params 的实际值。我无法在safeQuery 中尝试相同的操作,因为参数隐藏在... 中并且substitute(...) 不起作用。我试过了。
我也尝试过在get_data 的开头调用force(params),但这给出了相同的object not found 错误。
get_data <- function(con, params) {
force(params)
do.call("safeQuery",
list(con,
"SELECT *
FROM foo
WHERE bar IN ({vars*})",
vars=params)
}
那么,为什么params 会在标准调用中“迷失”?为什么do.call 有效,而force(params) 无效?是否可以使用标准评估来完成这项工作?
我不会撒谎:这种经历让我对如何编写函数和处理它们的参数感到困惑(从现在开始,我正在考虑只使用do.call)。如果可以在不过度扩展此问题范围的情况下提供提示,我将非常感激。
【问题讨论】:
-
你从来没有表现出你如何称呼
get_data,也许这可能有助于我理解出了什么问题。你能把它包括进去吗? -
@r2evans 哎呀。在第一次定义后立即通过调用 get_data 的示例编辑了问题。
-
如果
safeQuery的第一行是do.call(glue_sql, c(sql, list(...), .con=con)),它对我有用。我不确定为什么(有趣的问题),但它有效。 (我知道这只是您的段落“将 get_data 更改为...” 的不同观点。)
标签: r lazy-evaluation r-glue