如何将 SQL 传输到 R 的 dplyr 中?
How to pipe SQL into R's dplyr?
我可以在 R 中使用以下代码在任何通用 SQL 数据库中 select 不同的行。我会使用 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;")
我一直在成功地使用它,但想知道如何在其他 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;")
所以我 dplyr::left_join()
两个 SQL table,然后我想查看不同的行,并保留所有列。我是否如上所示将 SQL 代码通过管道传输到 R 中(仅使用 sql()
函数)?如果是这样,我将在 FROM table_name t
行的 table_name
中使用什么?
在我的第一个示例中,我使用了我从中提取的实际 table 名称。太明显了!但在这种情况下,如果我在没有数据库的情况下在 R 中工作,我习惯使用 magrittr 代词 .
或有时使用来自 rlang 的 .data
代词。
虽然我在 SQL 数据库中...那么我该如何处理这种情况?如何将我已知的工作 SQL 正确地传递到我的 R 代码中(使用正确的 table 名称代词)? dbplyr's reference page 是一个很好的起点,但并没有真正回答这个具体问题。
您似乎想要将自定义 SQL 代码与来自 dbplyr
的自动生成的 SQL 代码结合起来。为此,区分以下内容很重要:
DBI::db*
命令 - 在数据库上执行提供的 SQL 和 return 结果。
dbplyr
翻译 - 您使用远程连接到 table
您只能以特定方式组合这些。下面我根据您的特定用例给出了几个示例。所有人都假设 DISTINCT
是在您的特定 SQL 环境中接受的命令。
涵盖许多不同用例的参考示例
如果您不介意自我推销,我建议您看看我的 dbplyr_helpers
GitHub 存储库 (here)。这包括:
union_all
函数接受通过 dbplyr
访问的两个 table 并使用一些自定义 SQL 代码输出单个 table。
write_to_datebase
函数接受通过 dbplyr
访问的 table 并将其转换为可以通过 DBI::dbExecute
执行的代码
自动管道
当您使用定义了 SQL 翻译的标准 dplyr
动词时,dbplyr
会自动将您的代码通过管道传递给您的下一个查询。只要定义了 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()
当您随后调用 show_query(df)
R returns 以下自动生成的 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`
但格式不是很好。请注意,初始命令(左连接)显示为嵌套查询,在外部查询中有一个不同的查询。因此 df
是一个 R link 到由上述 sql 查询定义的远程数据库 table。
创建自定义 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()
然后调用 show_query(df)
R 应该 return 以下 SQL 代码(我说 'should' 因为我无法使用模拟 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`)
) nested_tbl
与前面的示例一样,df
是一个 R link 到由上述 sql 查询定义的远程数据库 table。
将 dbplyr 转换为 DBI
您可以从现有的 dbplyr
远程 table 获取代码并将其转换为可以使用 DBI::db*
.
执行的字符串
作为编写不同查询的另一种方式:
df1 = tbl_lazy(tmp_df, con = simulate_postgres())
df2 = tbl_lazy(tmp_df, con = simulate_postgres())
df = left_join(df1, df2, by = "col1")
custom_distinct2 = paste0("SELECT DISTINCT * FROM (",
as.character(sql_render(df)),
") AS nested_table")
local_table = dbGetQuery(db_connection, custom_distinct2)
这将 return 具有等效 sql 命令的本地 R 数据帧,按照前面的示例。
如果您想对 dbplyr 操作的结果进行自定义 SQL 处理,首先 compute()
可能会有用,它会创建一个新的 table(临时或永久) 与数据库中的结果集。下面的 reprex 显示了如果您依赖自动生成,如何访问新生成的 table 的名称。 (请注意,这依赖于 dbplyr 内部结构,如有更改,恕不另行通知——也许最好明确命名 table。)然后,像往常一样使用 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
由 reprex package (v0.3.0)
于 2020 年 1 月 1 日创建
如果您的 SQL 方言支持 CTEs,您还可以提取查询字符串并将其用作自定义 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
由 reprex package (v0.3.0)
于 2020 年 1 月 1 日创建
我可以在 R 中使用以下代码在任何通用 SQL 数据库中 select 不同的行。我会使用 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;")
我一直在成功地使用它,但想知道如何在其他 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;")
所以我 dplyr::left_join()
两个 SQL table,然后我想查看不同的行,并保留所有列。我是否如上所示将 SQL 代码通过管道传输到 R 中(仅使用 sql()
函数)?如果是这样,我将在 FROM table_name t
行的 table_name
中使用什么?
在我的第一个示例中,我使用了我从中提取的实际 table 名称。太明显了!但在这种情况下,如果我在没有数据库的情况下在 R 中工作,我习惯使用 magrittr 代词 .
或有时使用来自 rlang 的 .data
代词。
虽然我在 SQL 数据库中...那么我该如何处理这种情况?如何将我已知的工作 SQL 正确地传递到我的 R 代码中(使用正确的 table 名称代词)? dbplyr's reference page 是一个很好的起点,但并没有真正回答这个具体问题。
您似乎想要将自定义 SQL 代码与来自 dbplyr
的自动生成的 SQL 代码结合起来。为此,区分以下内容很重要:
DBI::db*
命令 - 在数据库上执行提供的 SQL 和 return 结果。dbplyr
翻译 - 您使用远程连接到 table
您只能以特定方式组合这些。下面我根据您的特定用例给出了几个示例。所有人都假设 DISTINCT
是在您的特定 SQL 环境中接受的命令。
涵盖许多不同用例的参考示例
如果您不介意自我推销,我建议您看看我的 dbplyr_helpers
GitHub 存储库 (here)。这包括:
union_all
函数接受通过dbplyr
访问的两个 table 并使用一些自定义 SQL 代码输出单个 table。write_to_datebase
函数接受通过dbplyr
访问的 table 并将其转换为可以通过DBI::dbExecute
执行的代码
自动管道
当您使用定义了 SQL 翻译的标准dplyr
动词时,dbplyr
会自动将您的代码通过管道传递给您的下一个查询。只要定义了 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()
当您随后调用 show_query(df)
R returns 以下自动生成的 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`
但格式不是很好。请注意,初始命令(左连接)显示为嵌套查询,在外部查询中有一个不同的查询。因此 df
是一个 R link 到由上述 sql 查询定义的远程数据库 table。
创建自定义 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()
然后调用 show_query(df)
R 应该 return 以下 SQL 代码(我说 'should' 因为我无法使用模拟 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`)
) nested_tbl
与前面的示例一样,df
是一个 R link 到由上述 sql 查询定义的远程数据库 table。
将 dbplyr 转换为 DBI
您可以从现有的 dbplyr
远程 table 获取代码并将其转换为可以使用 DBI::db*
.
作为编写不同查询的另一种方式:
df1 = tbl_lazy(tmp_df, con = simulate_postgres())
df2 = tbl_lazy(tmp_df, con = simulate_postgres())
df = left_join(df1, df2, by = "col1")
custom_distinct2 = paste0("SELECT DISTINCT * FROM (",
as.character(sql_render(df)),
") AS nested_table")
local_table = dbGetQuery(db_connection, custom_distinct2)
这将 return 具有等效 sql 命令的本地 R 数据帧,按照前面的示例。
如果您想对 dbplyr 操作的结果进行自定义 SQL 处理,首先 compute()
可能会有用,它会创建一个新的 table(临时或永久) 与数据库中的结果集。下面的 reprex 显示了如果您依赖自动生成,如何访问新生成的 table 的名称。 (请注意,这依赖于 dbplyr 内部结构,如有更改,恕不另行通知——也许最好明确命名 table。)然后,像往常一样使用 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
由 reprex package (v0.3.0)
于 2020 年 1 月 1 日创建如果您的 SQL 方言支持 CTEs,您还可以提取查询字符串并将其用作自定义 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
由 reprex package (v0.3.0)
于 2020 年 1 月 1 日创建