如何通过管道将 SQL 导入 R 的 dplyr?

Jas*_*ter 8 sql r dplyr r-dbi dbplyr

我可以在 R 中使用以下代码在任何通用 SQL 数据库中选择不同的行。我会使用,dplyr::distinct()但 SQL 语法不支持它。无论如何,这确实有效:

dbGetQuery(database_name, 
           "SELECT t.* 
           FROM (SELECT t.*, ROW_NUMBER() OVER (PARTITION BY column_name ORDER BY column_name) AS SEQNUM 
           FROM table_name t
           ) t 
           WHERE SEQNUM = 1;")
Run Code Online (Sandbox Code Playgroud)

我一直在成功地使用它,但想知道如何在其他 dplyr 步骤之后通过管道传输相同的 SQL 查询,而不是仅将其用作如上所示的第一步。这最好用一个例子来说明:

distinct.df <- 
  left_join(sql_table_1, sql_table_2, by = "col5") %>% 
  sql("SELECT t.* 
      FROM (SELECT t.*, ROW_NUMBER() OVER (PARTITION BY column_name ORDER BY column_name) AS SEQNUM 
      FROM table_name t
      ) t 
      WHERE SEQNUM = 1;")
Run Code Online (Sandbox Code Playgroud)

所以我有dplyr::left_join()两个 SQL 表,然后我想查看不同的行,并保留所有列。我是否如上所示将 SQL 代码通过管道传输到 R 中(仅使用该sql()函数)?如果是这样,我table_name将在线上使用什么FROM table_name t

在我的第一个示例中,我使用了我从中提取的实际表名。太明显了!但在这种情况下,我正在用管道和习惯使用 magrittr 代词. 或有时.data,如果我在没有数据库的情况下在 R 中工作时,我正在。

虽然我在 SQL 数据库中......那么我该如何处理这种情况?我如何正确地将我已知的工作 SQL 传输到我的 R 代码中(使用正确的表名代词)?dbplyr 的参考页面是一个很好的起点,但并没有真正回答这个特定问题。

Sim*_*.A. 6

看起来您想要将自定义 SQL 代码与从dbplyr. 为此,重要的是要区分:

  • DBI::db* 命令 - 在数据库上执行提供的 SQL 并返回结果。
  • dbplyr 翻译 - 使用远程连接到表的地方

您只能以某些方式组合这些。下面我根据您的特定用例给出了几个示例。所有人都假定这DISTINCT是在您的特定 SQL 环境中接受的命令。

涵盖许多不同用例的参考示例

如果您愿意自我推销,我建议您查看我的dbplyr_helpersGitHub 存储库(此处)。这包括:

  • union_all函数接收通过访问的两个表dbplyr并使用一些自定义 SQL 代码输出一个表。
  • write_to_datebase获取通过访问的表dbplyr并将其转换为可以通过以下方式执行的代码的函数DBI::dbExecute

自动配管

dbplyr当您使用dplyr定义了 SQL 翻译的标准动词时,会自动将您的代码导入到下一个查询中。只要定义了 sql 翻译,您就可以将许多管道(我一次使用 10 个或更多)链接在一起,(几乎)唯一的缺点是 sql 翻译的查询很难被人类阅读。

例如,请考虑以下情况:

library(dbplyr)
library(dplyr)

tmp_df = data.frame(col1 = c(1,2,3), col2 = c("a","b","c"))

df1 = tbl_lazy(tmp_df, con = simulate_postgres())
df2 = tbl_lazy(tmp_df, con = simulate_postgres())

df = left_join(df1, df2, by = "col1") %>%
  distinct()
Run Code Online (Sandbox Code Playgroud)

当您调用show_query(df)R 时,会返回以下自动生成的 SQL 代码:

SELECT DISTINCT *
FROM (

SELECT `LHS`.`col1` AS `col1`, `LHS`.`col2` AS `col2.x`, `RHS`.`col2` AS `col2.y`
FROM `df` AS `LHS`
LEFT JOIN `df` AS `RHS`
ON (`LHS`.`col1` = `RHS`.`col1`)

) `dbplyr_002`
Run Code Online (Sandbox Code Playgroud)

但没有那么好的格式。请注意,初始命令(左连接)显示为嵌套查询,在外部查询中有一个不同的查询。因此df是到由上述 sql 查询定义的远程数据库表的 R 链接。

创建自定义 SQL 函数

您可以通过管道dbplyr导入自定义 SQL 函数。管道意味着被管道传输的东西成为接收函数的第一个参数。

custom_distinct <- function(df){
  db_connection <- df$src$con

  sql_query <- build_sql(con = db_connection,
                         "SELECT DISTINCT * FROM (\n",
                         sql_render(df),
                         ") AS nested_tbl"
  )
  return(tbl(db_connection, sql(sql_query)))
}

df = left_join(df1, df2, by = "col1") %>%
  custom_distinct()
Run Code Online (Sandbox Code Playgroud)

当您然后调用show_query(df)R 时,应该返回以下 SQL 代码(我说“应该”,因为我无法使用模拟的 sql 连接来实现它),但格式不那么好:

custom_distinct <- function(df){
  db_connection <- df$src$con

  sql_query <- build_sql(con = db_connection,
                         "SELECT DISTINCT * FROM (\n",
                         sql_render(df),
                         ") AS nested_tbl"
  )
  return(tbl(db_connection, sql(sql_query)))
}

df = left_join(df1, df2, by = "col1") %>%
  custom_distinct()
Run Code Online (Sandbox Code Playgroud)

与前面的例子一样,df是一个 R 链接到由上述 sql 查询定义的远程数据库表。

将 dbplyr 转换为 DBI

您可以从现有的dbplyr远程表中获取代码并将其转换为可以使用DBI::db*.

作为编写不同查询的另一种方式:

SELECT DISTINCT * FROM (

SELECT `LHS`.`col1` AS `col1`, `LHS`.`col2` AS `col2.x`, `RHS`.`col2` AS `col2.y`
FROM `df` AS `LHS`
LEFT JOIN `df` AS `RHS`
ON (`LHS`.`col1` = `RHS`.`col1`)

) nested_tbl
Run Code Online (Sandbox Code Playgroud)

根据前面的示例,这将返回一个具有等效 sql 命令的本地 R 数据帧。


krl*_*mlr 5

如果要对 dbplyr 操作的结果进行自定义 SQL 处理,首先可能会很有用compute(),这会使用数据库上的结果集创建一个新表(临时或永久)。下面的 reprex 显示了如果您依赖自动生成,如何访问新生成的表的名称。(请注意,这依赖于 dbplyr 内部结构,如有更改,恕不另行通知——也许最好明确命名表。)然后,dbGetQuery()照常使用。

library(tidyverse)
library(dbplyr)
#> 
#> Attaching package: 'dbplyr'
#> The following objects are masked from 'package:dplyr':
#> 
#>     ident, sql

lazy_query <-
  memdb_frame(a = 1:3) %>%
  mutate(b = a + 1) %>%
  summarize(c = sum(a * b, na.rm = TRUE))

lazy_query
#> # Source:   lazy query [?? x 1]
#> # Database: sqlite 3.30.1 [:memory:]
#>       c
#>   <dbl>
#> 1    20

lazy_query_computed <-
  lazy_query %>%
  compute()

lazy_query_computed
#> # Source:   table<dbplyr_002> [?? x 1]
#> # Database: sqlite 3.30.1 [:memory:]
#>       c
#>   <dbl>
#> 1    20
lazy_query_computed$ops$x
#> <IDENT> dbplyr_002
Run Code Online (Sandbox Code Playgroud)

reprex 包(v0.3.0)于 2020 年 1 月 1 日创建

如果您的 SQL 方言支持CTE,您还可以提取查询字符串并将其用作自定义 SQL 的一部分,这可能类似于 Simon 的建议。

library(tidyverse)
library(dbplyr)
#> 
#> Attaching package: 'dbplyr'
#> The following objects are masked from 'package:dplyr':
#> 
#>     ident, sql

lazy_query <-
  memdb_frame(a = 1:3) %>%
  mutate(b = a + 1) %>%
  summarize(c = sum(a * b, na.rm = TRUE))

sql <-
  lazy_query %>%
  sql_render()

cte_sql <-
  paste0(
    "WITH my_result AS (", sql, ") ",
    "SELECT c + 1 AS d FROM my_result"
  )

cte_sql
#> [1] "WITH my_result AS (SELECT SUM(`a` * `b`) AS `c`\nFROM (SELECT `a`, `a` + 1.0 AS `b`\nFROM `dbplyr_001`)) SELECT c + 1 AS d FROM my_result"

DBI::dbGetQuery(
  lazy_query$src$con,
  cte_sql
)
#>    d
#> 1 21
Run Code Online (Sandbox Code Playgroud)

reprex 包(v0.3.0)于 2020 年 1 月 1 日创建