仅在查询 returns 少于 n_max 行时收集

collect only if query returns less than n_max rows

偶尔,当通过 ROracledbplyr 连接到我的 Oracle 数据库时,我将 运行 一个 dplyr::collect 操作获取比预期更多的数据R 可以处理。

这可能会使 R 崩溃,这通常表明我应该在获取数据之前进一步过滤或聚合数据。

如果能够在选择是否获取结果之前检查结果的大小(无需 运行查询两次),那就太好了。

让我们命名 collect2 允许这样做的 collect 的变体:

预期行为:

small_t <- con %>% tbl("small_table") %>%
  filter_group_etc %>%
  collect2(n_max = 5e6) # works fine

big_t   <- con %>% tbl("big_table")   %>%
  filter_group_etc %>%
  collect2(n_max = 5e6) # Error: query returned 15.486.245 rows, n_max set to 5.000.000

这可能吗?

我也对使用 ROracle / DBI 而不使用 dplyr 的解决方案持开放态度,例如:

dbGetQuery2(con, my_big_sql_query,n_max = 5e6) # Error: query returned 15.486.245 rows, n_max set to 5.000.000

编辑:

请参阅下面作为答案发布的部分解决方案,不是最优的,因为一些时间浪费在获取我没有用的数据上。

这并不能解决您在评论中提到的关于花费资源来获取两次查询的问题,但它似乎确实有效(至少针对我的 MySQL 数据库——我不知道没有 Oracle 数据库来测试它):

collect2  <- function(query, limit = 20000) {

  query_nrows  <- query %>% 
    ungroup() %>% 
    summarize(n = n()) %>% 
    collect() %>% 
    pull('n')


  if(query_nrows <= limit) {
    collect(query)
  } else {
    warning("Query has ", query_nrows,"; limit is ", limit,". Data will not be collected.")
  }

}

我看不出有什么方法可以在不实际 运行 查询的情况下测试查询结果中的行数。但是,使用这种方法,您总是强制首先在数据库中计算行号,如果超过 20,000(或任何行限制)则拒绝收集。

您实际上可以在 一个 SQL 查询 中实现您的目标:

使用 dplyr 的 mutate 而不是汇总,将 行计数 (n) 作为额外的列 添加到数据中,然后设置n < n_limit 作为过滤条件。此条件对应于 SQL 中的 having 子句。如果行数大于列表,则不会收集任何数据。否则收集所有数据。您可能希望在最后删除行数列。

此方法适用于大多数数据库。我已经使用 PostgreSQL 和 Oracle 验证了这一点。

copy_to(dest=con, cars, "cars")
df <- tbl(con, "cars")
n_limit <- 51
df %>% mutate(n=n()) %>% filter(n < n_limit) %>% collect

但是,它不适用于 SQLite。想知道为什么会这样,可以查看dplyr代码生成的SQL语句:

df %>% mutate(n=n()) %>% filter(n < n_limit) %>% show_query

<SQL>
SELECT *
FROM (SELECT "speed", "dist", COUNT(*) OVER () AS "n"
FROM "cars") "rdipjouqeu"
WHERE ("n" < 51.0)

SQL 包含 window 函数 (count(*) over ()),SQLite 不支持。

因此,如果没有 运行 查询,您将无法检查结果的大小。

现在的问题是要么在服务器端缓存结果并测试大小,要么简单地将一些 "insurance" 放在 R 端,这样我们就不会收到太多行。

在后一种情况下,如何简单地:

small_t <- con %>% tbl("small_table") %>%
  filter_group_etc %>%
  head(n=5e6) %>%
  collect()

如果你得到 5e6 行,它们可能溢出了;我们无法区分溢出和 5e6 行,但这似乎是在数据库中获得单次执行的一个小代价?如果您真的担心,请将 5e6 设置为 5000001。 (并且 5000000L5000001L 将是更好的选择,以便数据库将它们视为整数。)

如果你担心连接速度慢,这就不太管用了,但如果你只是担心 R 中的内存溢出,这是一种便宜的保险,不会给服务器增加额外的负载.

您也可以使用slice_sample()函数。

collected_data <- table %>% 
  slice_sample(n = 30) %>% 
  collect()