R如何像SAS一样写宏

时间:2018-05-14 15:27:14

标签: r macros rodbc

我有一个非常长的sqlquery,只需更改日期就需要运行几次。我擅长SAS但对R来说很陌生,所以我很难写出类似于SAS的东西。

df <- sqlQuery(datamart, paste0("Select xxxxxxxxxxxxxxxxxx from xxxxx
where date = '28Feb2018'"), as.is=TRUE, stringsAsFactors = FALSE)

你能分享一些经验吗?

谢谢!

于6/5/2018编辑:

我根据下面的答案编辑了代码,但在正确运行代码时仍然遇到一些麻烦。 目前我的代码变为:

safeqry <- function(date_string)
{require(RODBCext)
qry_string <- paste0("DELETE FROM [T_SPP] WHERE [BkDt]<=#?","#")
  parms <- data.frame(date_string, stringsAsFactors=FALSE)
  sqlExecute(access, qry_string, parms, fetch=TRUE)}
safeqry('2016-04-30')

错误是:

  

42000 -3100 [Microsoft] [ODBC Microsoft Access驱动程序]查询表达式中的日期语法错误&#39; [BkDt]&lt; =#Pa_RaM000&#39;。   [RODBCext]错误:SQLExecute失败   另外:警告信息:   在sqlExecute(access,qry_string,parms,fetch = TRUE)中:

其他代码是

query_delete = function (table, date_col, date) {
paste0('DELETE FROM [',table,'] WHERE [',date_col,']<=#',date, '#')}
sqlQuery(access, query_delete("T_SPP", "BkDt", "2018-04-30"),as.is = TRUE, stringsAsFactors = FALSE)

错误是

  

[1]&#34; [RODBC]错误:无法SQLExecDirect&#39; DELETE FROM [T_SPP] WHERE [BkDt]&lt; =#2018-04-30#&#39;&#34; < / p>

3 个答案:

答案 0 :(得分:5)

与SAS宏相当的R是一个函数。因此,您编写一个函数,将日期作为参数,然后将日期传递给查询。

最简单的方法是通过字符串操作:

safeqry <- function(date_string)
{
    require(RODBCext)
    qry_string <- paste0("select xxxxx from yyy where date = ?")
    parms <- data.frame(date_string, stringsAsFactors=FALSE)
    sqlExecute(datamart, qry_string, parms, fetch=TRUE)
}

但是,这通常是不安全的,因为人们可以将恶意字符串传递给您的函数,导致它执行Bad Things.而是考虑使用RODBCext package来运行参数化查询而不是弄乱字符串:

{{1}}

答案 1 :(得分:3)

最简单的方法如下所示:

macro1 <- function(dt) {
  qry <- paste0("select xxxxxxxxxx from xxxx where date='", dt, "'")
  sqlQuery(datamart, qry, as.is=TRUE, stringsAsFactors=FALSE)
}

但它有一些问题:

  • 它假定连接对象datamart在父(和/或全局)环境中可用且有效;如果您使用不同的连接进行测试,我保证会以您不期望的方式咬你;和
  • 很容易SQL injection(漫画:xkcd 327
  • 如果您的参数为空或长度为2或更长,则可能无法执行您想要的操作

更强大的功能类似于:

macro2 <- function(dt, con) {
  if (length(dt) == 0L) {
    stop("'dt' is not length 1")
  } else if (length(dt) > 1L) {
    warning("'dt' has length > 1 and only the first element will be used")
    dt <- dt[[1L]]
  }
  qry <- sprintf("select xxxxxxxxxx from xxxx where date='%s'", sQuote(dt))
  sqlQuery(con, qry, as.is=TRUE, stringsAsFactors=FALSE)
}

虽然更好的解决方案是使用变量绑定(&#34;参数化查询&#34;),这对每种数据库类型都是唯一的。正如Hong Ooi建议的那样,RODBCextRODBC连接提供了此功能,但您需要更多数据库特定的连接。

如果你想变得有点懒惰并且感觉安全,那么连接总是在全球范围内,你可能会想做类似的事情:

macro2 <- function(dt, con=datamart) ...

哪个会起作用,但我仍然反对它。经验表明,显式通常更安全,更容易排除故障。

从这里开始,可以使用循环,无论是{JasonAizkalns建议的for循环,还是可能类似:

answers <- lapply(vector_of_dates, macro2)

答案 2 :(得分:1)

有几种方法可以做到这一点,但既然你说你是R的新手,这可能是一种自然的方法。首先,创建一个函数,允许您更改select_colstbldate并返回一个字符串:

make_query <- function(select_cols, tbl, date) {
  paste0("SELECT ", select_cols, " FROM ", tbl, " WHERE date = '", date, "';")
}

make_query("*", "my_table", "28Feb2018")
[1] "SELECT * FROM my_table WHERE date = '28Feb2018';"
make_query("*", "different_table", "28Feb2018")
[1] "SELECT * FROM different_table WHERE date = '28Feb2018';"

然后你可以创建一个日期向量来循环:

various_dates <- c("28Feb2018", "01Mar2018", "02Mar2018")

for (date in seq_along(various_dates)) {
  make_query("*", "my_table", various_dates[date])
}

当然,您可以修改循环体以使用sqlQuery函数:

for (date in seq_along(various_dates)) {
  sqlQuery(datamart, make_query("*", "my_table", various_dates[date]), 
           as.is = TRUE, stringsAsFactors = FALSE)
}

由于您希望保存结果,因此您可以预先分配与日期数相同的空列表并保存这些结果:

df <- vector("list", length(various_dates))
for (date in seq_along(various_dates)) {
  df[[date]] <- sqlQuery(datamart, make_query("*", "my_table", various_dates[date]), 
           as.is = TRUE, stringsAsFactors = FALSE)
}