【问题标题】:Complicated filter statements in dbplyr using lists of listsdbplyr 中使用列表列表的复杂过滤器语句
【发布时间】:2018-10-25 19:04:33
【问题描述】:

在 SQL 中你可以这样写:

SELECT *
FROM table
WHERE (var1, var2, var3, var4) IN (("var1-1", "var2-1", "var3-1", "var4-1"),
                                   ("var1-2", "var2-2", "var3-2", "var4-2"))

这意味着抓取 (var1 == "var1-1" and var2 == "var2-1" and var3 == "var3-1" and var4 == "var4-1") 或 (var1 == "var1-2" and var2 == "var2-2" and var3 == "var3-2" and var4 == "var4-2")

有没有办法以编程方式在 dbplyr 中进行类似的查询?

例如,假设我有一个 tibble:

tribble(
    ~var1,     ~var2,    ~var3,    ~var4,
    "var1-1",  "var2-1", "var3-1", "var4-1",
    "var1-2",  "var2-2", "var3-2", "var4-2"
  )

我可以使用某种函数来让 dbplyr 构建一个类似于上面的 SQL 语句吗?

【问题讨论】:

  • var1var1-1, var1-2 还是var1-1, var2-1, etc 中?

标签: r dplyr dbplyr


【解决方案1】:

1) 使用inner_join

library(dplyr)

# test data
v <- paste0("var", 1:4)
DF1 <- as.data.frame(t(outer(v, 1:3, paste, sep = "-")), stringsAsFactors = FALSE)
DF2 <- as.data.frame(t(outer(v, 2:4, paste, sep = "-")), stringsAsFactors = FALSE)

DF1 %>% inner_join(DF2)

给予:

Joining, by = c("V1", "V2", "V3", "V4")
# A tibble: 2 x 4
  V1     V2     V3     V4    
  <chr>  <chr>  <chr>  <chr> 
1 var1-2 var2-2 var3-2 var4-2
2 var1-3 var2-3 var3-3 var4-3

2) 在基础 R 中,我们可以使用 merge

merge(DF1, DF2)

intersect

intersect(DF1, DF2)

3) 在 dbplyr 中:

library(dbplyr)

# set up backend using DF1 and DF2 from (1)
con <- DBI::dbConnect(RSQLite::SQLite(), path = ":memory:")
copy_to(con, DF1, "DF1")
copy_to(con, DF2, "DF2")

DF1_db <- tbl(con, "DF1")
DF2_db <- tbl(con, "DF2")
DF1_db %>% inner_join(DF2_db)

给予:

Joining, by = c("V1", "V2", "V3", "V4")
# Source:   lazy query [?? x 4]
# Database: sqlite 3.19.3 []
  V1     V2     V3     V4    
  <chr>  <chr>  <chr>  <chr> 
1 var1-2 var2-2 var3-2 var4-2
2 var1-3 var2-3 var3-3 var4-3

如果您有一个 tibble 和一个数据库表,您将需要使用 copy_to 将 tibble 复制到数据库或将数据库表抓取到 R 中。inner_join 不能混合源。

【讨论】:

  • 或者,由于您不想添加任何列,dplyr 也将其称为semi_join。在这种情况下也是如此,因为 DF2 没有额外的列。
  • 抱歉,澄清一下,当从数据库中提取数据时,这必须在 dbplyr 中工作。是否可以将 tibble 内部连接到 tbl_lazy 对象?
  • 见答案第(3)部分。
  • 感谢您为回答付出了多少努力。不幸的是,在我的情况下,我没有对数据库的写入权限。
  • 如果 tibble 只有几行,您可以动态创建一个 sql 语句,以您在问题中描述的方式对其进行硬编码。 paste("select * from mytable where (var1, var2, var3, var4) in", toString(paste("(", apply(DF1, 1, toString), ")")))
【解决方案2】:

使用带有purrrrlang 的一些高级R 编程,您应该能够为filter() 创建一个表达式,我认为,它确实可以完成您所要求的事情,这是一个使用示例mtcars。我使用适用于该示例的数据重新创建了您的 tribble。我还添加了内联 cmets 来帮助解释每个步骤的作用:

library(dbplyr, warn.conflicts = FALSE)
library(dplyr, warn.conflicts = FALSE)
library(purrr, warn.conflicts = FALSE)
library(DBI, warn.conflicts = FALSE)
library(rlang, warn.conflicts = FALSE)

con <- DBI::dbConnect(RSQLite::SQLite(), path = ":dbname:")
db_mtcars <- copy_to(con, mtcars)

filters <- tribble(
  ~gear, ~carb,    
  4,     4,
  3,     1
)
# transpose() converts rows into lists entries
filter_expr <- transpose(filters) %>%
  # iterate through each row
  map(~{
    # iterate through each pair ie: gear == 4
    imap(.x, ~expr(!!sym(.y) == !!.x)) %>%
      # creates one call in the row by 
      # "collapsing" all pairs, separating them with &
      reduce(function(x, y) expr((!!x & !!y)))
    }) %>%
  # creates one call collapsing all row calls 
  # into a single one, separating them with |
  reduce(function(x, y ) expr(!!x | !! y))
# the resulting call
filter_expr
#> (gear == 4 & carb == 4) | (gear == 3 & carb == 1)

filtered_mtcars <- db_mtcars %>%
  # Use bang-bang (!!) to execute the new call
  filter(!! filter_expr)

filtered_mtcars
#> # Source:   lazy query [?? x 11]
#> # Database: sqlite 3.22.0 []
#>     mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
#>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1  21       6  160    110  3.9   2.62  16.5     0     1     4     4
#> 2  21       6  160    110  3.9   2.88  17.0     0     1     4     4
#> 3  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1
#> 4  18.1     6  225    105  2.76  3.46  20.2     1     0     3     1
#> 5  19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4
#> 6  17.8     6  168.   123  3.92  3.44  18.9     1     0     4     4
#> 7  21.5     4  120.    97  3.7   2.46  20.0     1     0     3     1

show_query(filtered_mtcars)
#> <SQL>
#> SELECT *
#> FROM `mtcars`
#> WHERE ((`gear` = 4.0 AND `carb` = 4.0) OR (`gear` = 3.0 AND `carb` = 1.0))

dbDisconnect(con)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-26
    • 1970-01-01
    • 2011-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多