如何一次性将完整的 R 数据框插入 SQL 表

Posted

技术标签:

【中文标题】如何一次性将完整的 R 数据框插入 SQL 表【英文标题】:How can I insert a complete R dataframe in one go into SQL table 【发布时间】:2017-02-21 05:51:21 【问题描述】:

如何在存储过程的帮助下使用 R 语言一次性将完整的 R data.frame 插入 SQL 表中,但不使用循环?

我使用的是 Microsoft Server 2012。

这是我的 R 代码:

library(RODBC)
dbhandle <- odbcDriverConnect('driver=SQL Server;server=MySERVERName;database=Testing;trusted_connection=true;')
f= data.frame(
    age= c(21,22,23,24),
  name= c("fifi", "jojo", "jj", "arbi"), stringsAsFactors = FALSE)
sqlQuery(dbhandle,paste("EXEC BOinsert @age=", f$age, ", @name=", f$name)) 

这是我的 SP:

ALTER proc [dbo].[BOinsert]

@age int,
@name nvarchar(50)
as 
Insert into Student(Age, Name) values (@age, @name)

【问题讨论】:

您的data.frame 通常包含多少行?以这种方式插入一万行或更多行会很慢(搜索“批量插入”)。 是否必须使用存储过程来插入数据? @RYoda yes 使用 SP 是强制性的,我也分享了我的插入 SP。 是的,我在该数据框中有 21-400 多条记录,我需要使用 SP 将它们插入到 SQL 表中。 您使用的是哪个数据库? Microsoft SQL Server 版本 xxxx(如您的连接字符串所示)? 【参考方案1】:

我建议使用RODBCext

library(RODBCext)

f= data.frame(
  age= c(21,22,23,24),
  name= c("fifi", "jojo", "jj", "arbi"), stringsAsFactors = FALSE)

sqlExecute(dbhandle,
           "EXEC BOinsert @age = ?, @name = ?",
           data = f)

严格来说,这是对您的数据运行for 循环并为每一行执行一次存储过程。但我认为它是在C 代码中完成的,不必担心让它工作。这是给你的一行代码。它还将为您处理所有引用,因此您不必担心 SQL 注入攻击。

编辑:

刚刚在我的系统上运行了一个包含 1000 行数据框的测试。 sqlExecute 能够在大约 1.52 秒内全部加载完毕。 sqlQuery 花了大约 1.76 秒。所以性能提升不是很大,但至少sqlExecute 看起来不像是一个循环。

【讨论】:

感谢您的性能指示! RODBCext 在内部使用循环,因此性能增益“仅”是由于准备好的语句避免了重新解析 SQL 代码。请参阅第 149 行的源代码:github.com/zozlak/RODBCext/blob/master/R/sqlExecute.R -> for(row in seq_len(nrow(data))) ... 在这种情况下,我能想象的唯一可能提高性能的另一件事是生成一个脚本来将所有内容写入一个INSERT。在 R 中这样做很容易,但不是我们需要的存储过程。编写一个存储过程来解析向量是可能的,但不是微不足道的。我必须相信在我尝试之前会有非常显着的性能提升。 是否有任何其他版本的 RODBCext 包适用于 R 版本 3.6.3?【参考方案2】:

mysql 可以使用 RMySQL 来完成

install.packages("RMySQL")
library(RMySQL)
mydb = dbConnect(MySQL(), user='your_user_name', password='your_password', dbname='your_database_name', host='localhost') 

dbListTables(mydb)


#create data frame

ds <- data.frame(a=1:10, b= 21:30)

#adding ds data frame to your database
dbWriteTable(mydb, name='db', value=ds)

#then check your database this will be added as table in name of db

【讨论】:

OP 似乎使用 Microsoft SQL 服务器(请参阅连接字符串)并且必须使用存储过程。所以我想你的答案很好,但不是 OP 需要的。 @Mj_7 在多次试验后我也遇到了很多错误。加载所需的包:DBI > mydb = dbConnect(MySQL(), dbname='Testing', host='DGS') .local(drv, ...) 中的错误:无法连接到数据库:错误:无法连接到 'DGS' (0) 上的 MySQL 服务器 > > dbListTables(mydb) dbListTables(mydb) 中的错误:找不到对象 'mydb' > > > > @Eqra 在您的代码中的 MySQL() 中存在一些错误:尝试使用 MySQL() 并插入用户名和密码以将您的数据库与您的新连接连接【参考方案3】:

添加于 2017 年 2 月 22 日:

如果您无法更改每次调用只能插入一行的存储过程(“SP”),则没有简单的方法可以避免循环。这意味着每个存储过程调用(= 每个 data.frame 行)的网络往返。

您的替代方案是(太复杂了,无法在此处详细展示):

    使用bcp.exebulk insert 语句将data.frame 批量插入 到临时表或临时表中。然后执行一条 SQL 语句,遍历 temp/staging 表中的所有行并调用您的插入 SP。这有效地将循环从客户端 (R) 转移到服务器端 (SQL 服务器),这要快得多,因为插入的每一行都不需要网络往返。详情见:MS-SQL Bulk Insert with RODBC

    仍然循环遍历 R 代码中的所有行,但创建一个 SQL 语句块,一次插入多行(例如,一次插入 100 行)。这通过您一次发送的行数减少了网络往返。

    创建一个以 e 形式接收 data.frame 内容的包装器 SP。 G。 CSV 文本字符串,对其进行解析并为每个数据行调用您的插入 SP。

    [添加于 2017 年 2 月 23 日] 您可以使用支持准备好的语句的包 RODBCext。这个“仅”隐藏了循环,但仍然在客户端内部循环(每行插入一个网络/服务器往返)但节省了一些时间,因为不能再次解析和“准备”SQL语句(执行计划等)。

解决方案 2 是最容易实现的,但您必须关心 ODBC 驱动程序的最大语句长度(我猜大约是 1000、2000、4000 或 8000 个字符)。

原答案:

由于您必须使用存储过程来插入每一行,因此您最终会得到一个缓慢的循环解决方案(如果您不需要插入很多行,这是可以的):

library(RODBC)

f <- data.frame(age = c(21,22,23,24), name = c("fifi", "jojo", "jj", "arbi"), stringsAsFactors = FALSE)

dbhandle <- odbcDriverConnect('driver=SQL Server;server=MySERVERName;database=Testing;trusted_connection=true;')

for (i in 1:nrow(f)) 
  sqlQuery(dbhandle, paste0("EXEC BOinsert @age=", f[i,]$age, ", @name='", f[i,]$name, "'"))
  # print(paste0("EXEC BOinsert @age=", f[i,]$age, ", @name='", f[i,]$name, "'"))


odbcClose(dbhandle)

【讨论】:

我有 400 多行要插入,目前我正在使用循环,这是我不想使用的。 @Eqra 抱歉,我忘记了循环的要点,因为我认为对于 400 行来说,它对性能并不重要。 “400+”是什么意思?超过 400 行?还是超过40万? 它的性能非常关键,计数可能会变化,可能会达到 1000。我的经理要求不要循环。【参考方案4】:

我的第一个答案的备选方案 2创建一个一次插入多行的 SQL 语句块)可以这样实现:

library(RODBC)

f <- data.frame(age = c(21,22,23,24), name = c("fifi", "jojo", "jj", "arbi"), stringsAsFactors = FALSE)

dbhandle <- odbcDriverConnect('driver=SQL Server;server=MySERVERName;database=Testing;trusted_connection=true;')

block.size.in.rows <- 2  # you should use a much bigger value like 100 (2 is only for demonstration purposes here)
sql <- ''

for (i in 1:nrow(f)) 
  # sqlQuery(dbhandle, paste0("EXEC BOinsert @age=", f[i,]$age, ", @name='", f[i,]$name, "'"))
  sql <- paste0(sql, "EXEC BOinsert @age=", f[i,]$age, ", @name='", f[i,]$name, "'", "; \n")
  if ((i %% block.size.in.rows) == 0) 
    cat(paste0("Sending block:\n"))
    cat(sql)      # debug
    sqlQuery(dbhandle, sql)
    sql <- ''
  


if (nchar(sql) > 0) 
  cat(paste0("Sending block:\n"))
  cat(sql)      # debug
  sqlQuery(dbhandle, sql)


odbcClose(dbhandle)

它将通过一个服务器往返在一个块中发送多个 SQL 语句:

Sending block:
EXEC BOinsert @age=21, @name='fifi'; 
EXEC BOinsert @age=22, @name='jojo'; 
Sending block:
EXEC BOinsert @age=23, @name='jj'; 
EXEC BOinsert @age=24, @name='arbi'; 

【讨论】:

以上是关于如何一次性将完整的 R 数据框插入 SQL 表的主要内容,如果未能解决你的问题,请参考以下文章

将数据框从 R 插入 SQL 的有效方法

响应式可折叠表一次一个:

如何在 SQL Server 中插入多行?

mysql 怎么给一个表一次增加多个字段?

mysql批量插入数据

是否可以一次将整个 VB.NET 数据表插入 SQL Server