2017-11-11 125 views
2

有没有什么办法可以把sqldf查询放到用户定义的函数中?我已经通过了这个:http://r.789695.n4.nabble.com/Passing-Multiple-Variable-Into-SQLDF-Statement-as-parameters-of-function-td4636147.htmlR call variable inside sqldf把sqldf放在r函数里

我的示例代码会是这样的:

db1 = data.frame(a = c(1,2,3), b = c("a","b","c")) 
db2 = data.frame(a = c(1,2,3), b = c("b","a","c")) 
db = list(db1,db2) 

extrct = function(x){ 
Example=paste0("select * from", x , "where b 
='","b", "'") 
sqldf(Example,verbose=TRUE) 
} 

我有很多的数据库,这将是很容易编写SAS宏象代码,只要sqldf工作的函数中提取数据。否则,我已经为一些小的进程编写了R代码,但是有很多复杂的SQL程序在sqldf中会更容易。提前致谢。

+1

您的SQL表达式格式不正确。使用这一行,你会很好的去:'Example = paste0(“select * from”,'x',“where b ='”,“b”,“'”,collapse =“”)''。请记住测试函数的内部部分以确保它们产生预期的输出。 – lmo

回答

2

这是我如何写它。 sqldf更自然地处理字符串,而不是使用nse。因此,只需传入要使用源的data.frame的字符串/名称即可。

library(sqldf); requireNamespace("checkmate") 
db1 <- data.frame(a = c(1,2,3), d = c("a","b","c"), stringsAsFactors = F) 

extract <- function(table_name, criteria_d) { 
    checkmate::assert_character(table_name, min.chars=1, len=1, any.missing=F) 
    checkmate::assert_character(criteria_d, min.chars=1, len=1, any.missing=F) 

    # Half-way attempt to prevent sql-injection. Values would need to be only numbers, letters, and underscores. 
    checkmate::assert_character(table_name, pattern="^\\w+$", len=1, any.missing=F) 
    checkmate::assert_character(criteria_d, pattern="^\\w+$", len=1, any.missing=F) 

    sql <- paste0("select * from [", table_name , "] where d ='", criteria_d, "'") 

    cat("Executing: `", sql, "`\n", sep="") 
    sqldf(sql, verbose=F) 
} 

extract("db1", "b") 

如果由于某种原因,你会不知道该变量的字符串/名称,这相当于:extract(quote(db1), "b")

一些笔记。

  1. 我将变量的名称改为'd'以使事情更清楚。
  2. 我认为db2db与您的方案无关。
  3. 我试图不要更改你的代码太多。如果此功能连接到真实数据库,请防止sql injection
  4. 如果你的SQL稍微复杂一点,可以考虑使用glue::glue_sql()

编辑回应@ Sayak的评论

使用purrr::map_df()遍历data.frame

c("db1", "db2") %>% 
    purrr::map_df(extract, "b") 

的载体和结合的结果成单个数据帧:

Executing: `select * from [db1] where d ='b'` 
Executing: `select * from [db2] where d ='b'` 
    a d 
1 2 b 
2 1 b 

这很漂亮,它不需要后续调用dplyr::bind_rows()

如果需要改变标准(即,所以它并不总是“B”),使用purrr::pmap_df()带包装的投入作为data.frame(其列与您extract()函数的参数:

ds_input <- tibble::tribble(
    ~table_name, ~criteria_d, 
    "db1",   "b", 
    "db1",   "c", 
    "db2",   "c" 
) 

ds_input %>% 
    purrr::pmap_df(extract) 

# Executing: `select * from [db1] where d ='b'` 
# Executing: `select * from [db1] where d ='c'` 
# Executing: `select * from [db2] where d ='c'` 
# a d 
# 1 2 b 
# 2 3 c 
# 3 3 c 
+0

非常感谢这个答案。如果我在每个数据框中都有多个数据框和相同的列,请问只有一个问题,如果在数据框列表中定义了类似的提取“b”的提取方法?或者有什么办法可以对函数提取做同样的事情吗? – Sayak

3

试试这个:

library(sqldf) 

extract <- function(x, envir = parent.frame(), verbose = TRUE, ...) { 
    fn$sqldf("select * from [$x] where b = 'b'", envir = envir, verbose = verbose, ...) 
} 

# sample runs 
extract("db1") 
extract("db2") 

Map(extract, c("db1", "db2")) 

db <- setNames(db, c("db1", "db2")) 
lapply(names(db), extract, envir = list2env(db)) 

如果我们在最后一行改变这一则输出将有分量的名字,但在其他方面是相同的:

sapply(names(db), extract, envir = list2env(db), simplify = FALSE) 
+0

非常感谢,这真的非常有帮助。 – Sayak