4

我正在使用 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)
}

(正如这个答案所推荐的)工作得很好,因为do.call在将它们发送到之前评估列表中的参数safeQuery

我不明白为什么这首先是必要的。毕竟, 的值params不会在任何一步被修改到glue_sql,所以它应该仍然可用。

链接的答案讨论了使用substitute(我还阅读了有关该主题的这篇 R-bloggers 帖子)将参数的名称替换为调用者的名称(或者如果直接给出参数值,则使用其实际值),但这并没有在我的情况下不起作用。修改get_data使用substitute

get_data <- function(con, params) {
  do.call("safeQuery",
          list(con,
               "SELECT *
                FROM foo
                WHERE bar IN ({vars*})",
               vars=substitute(params))
}

导致以下 SQL 来自glue_sql

SELECT *
FROM foo
WHERE bar IN (params)

而不是 的实际值params。我无法在其中尝试相同的方法,safeQuery因为参数隐藏在...其中并且substitute(...)不起作用。我试过了。

我也尝试过force(params)在开头调用get_data,但这给出了同样的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从现在开始只使用)。如果可以在不过度扩展这个问题的范围的情况下给出提示,我将非常感激。

4

1 回答 1

1

我不完全清楚为什么会这样,但确实如此。

safeQuery <- function(con, sql, ...) {
  dots  = list(...)
  dots
}

然后当你打电话时get_data("foo_con", params = 1:3),你会得到:

$`vars`
[1] 1 2 3

所以现在我们在一个命名列表中有参数,这意味着你应该使用glue_data(或glue_data_sql):

safeQuery <- function(con, sql, ...) {
  dots  = list(...)
  glue_data_sql(.x = dots, sql, .con=con)
  # More code...
}

现在当你打电话时get_data("foo_con", params = 1:3),你会得到:

<SQL> SELECT *
FROM foo
WHERE bar IN (1, 2, 3)

替代版本:

问题在于正在评估参数的环境。强制执行此操作的一种方法是传递您想要的环境:

safeQuery <- function(con, sql, ..., .envir = parent.frame()) {
  dots  = list(...)
  glue_sql(sql, ..., .con=con, .envir = .envir)
  # More code...
}

get_data <- function(con, params) {

  env <- environment()

  safeQuery(con,
            "SELECT *
            FROM foo
            WHERE bar IN ({vars*})",
            vars=params, .envir = env)
}

get_data("foo_con", params = 1:3)
<SQL> SELECT *
FROM foo
WHERE bar IN (1, 2, 3)
于 2019-08-01T01:14:39.597 回答