R 使用 dbi 更新数据库

Posted

技术标签:

【中文标题】R 使用 dbi 更新数据库【英文标题】:R Updating database with dbi 【发布时间】:2018-04-01 22:18:22 【问题描述】:

我在 R 中使用 DBI 进行了一些工作,第一个问题更多的是最佳实践,因为目前将新数据附加到 DB 所花费的时间比我希望的要多。其次是我在尝试更新数据库中的旧信息时收到的错误。这是我当前向数据库中的现有表插入新数据时的工作流程:

con <- dbConnect(odbc(), "myDSN")

# Example table 1
tbl1 <- tibble(Key = c("A", "B", "C", "D", "E"),
               Val = c(1, 2, 3, 4, 5))

# Original table in DB
dbWriteTable(con, "tbl1", tbl1, overwrite = TRUE)

# Link to Original table
db_tbl <- tbl(con, in_schema("dbo", "tbl1"))

# New data
tbl2 <- tibble(Key = c("D", "E", "F", "G", "H"),
               val = c(10, 11, 12, 13, 14))

# Write it to Staging
dbWriteTable(con, "tbl1_staging", tbl2, overwrite = TRUE)

# Get a link to staging
db_tblStaging <- tbl(con, in_schema("dbo", "tbl1_staging"))

# Compare Info
not_in_db <- db_tblStaging %>% 
  anti_join(db_tbl, by="Key") %>% 
  collect()

# Append missing info to DB
dbWriteTable(con, "tbl1", not_in_db, append = TRUE)

# Voila!
dbReadTable(con, "tbl1")

这可以解决问题,但我正在寻找更好的解决方案,因为我讨厌代码的 collect() 部分,这意味着我将一些东西带到 R 内存中(据我所知)当我有更大的数据时,将来可能会成为问题。我希望能像这样工作,这将允许我将新数据快速附加到 DB,而无需访问内存。

# What I hoped to have
db_tblStaging %>% 
  anti_join(db_tbl, by="Key") %>% 
  dbWriteTable(con, "tbl1", ., append = TRUE)

第二个问题是更新现有表。这是我尝试过的,但是会出现错误并且无法弄清楚。这是我试图复制答案的链接:How to pass data.frame for UPDATE with R DBI。我想用 val 中的新值更新键 E 和 D。

# Trying to update tbl1
update_values <- db_tblStaging %>% 
  semi_join(db_tbl, by="Key") %>% 
  collect()

update <- dbSendQuery(con, 'UPDATE tbl1 
                            SET "val" = ? 
                            WHERE Key = ?')

dbBind(update, update_values)

Error in result_bind(res@ptr, as.list(params)) : 
  nanodbc/nanodbc.cpp:1587: 42000: [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Incorrect syntax near the keyword 'Key'. 

包裹是否发生了某种变化?我无法发现我的语法错误。

【问题讨论】:

【参考方案1】:

考虑在表暂存上传后运行纯 SQL,因为看起来您需要 NOT EXISTS(以避免重复)和 UPDATE INNER JOIN(用于现有记录)。这避免了任何 R 客户端查询导入和导出。

Key 是 SQL Server 中的保留字。因此,请使用方括号对其进行转义。

apn_sql <- "INSERT INTO dbo.tbl (s.[Key], s.[Val])
            SELECT s.[Key], s.[Val] FROM dbo.tbl_staging s
            WHERE NOT EXISTS
              (SELECT 1 FROM dbo.tbl t
               WHERE t.[Key] = s.[Key])"

dbSendQuery(con, apn_sql)

upd_sql <- "UPDATE t
            SET t.Val = s.Val
            FROM dbo.tbl t 
            INNER JOIN dbo.tbl_staging s 
               ON t.[Key] = s.[Key]"

dbSendQuery(con, upd_sql)

Rextester demo

事实上,SQL Server 有MERGE 查询可以在一次调用中处理这两个问题:

MERGE dbo.tbl AS Target
USING (SELECT [Key], [Val] FROM dbo.tbl_staging) AS Source
  ON (Target.[Key] = Source.[Key])
WHEN MATCHED THEN
    UPDATE SET Target.Val = Source.Val
WHEN NOT MATCHED BY TARGET THEN
    INSERT ([Key], [Val])
    VALUES (Source.[Key], Source.[Val]);

Rextester demo

【讨论】:

以上是关于R 使用 dbi 更新数据库的主要内容,如果未能解决你的问题,请参考以下文章

Perl DBI / FreeTDS / SQL-Server:如何插入/更新 BLOB varbinary(max) 数据?

Perl DBI / MS ODBC Driver (LinuxL:RHEL) / SQL-Server:如何插入/更新 BLOB varbinary(max) 数据?

R将tbl对象更新为红移

如何在 R 中使用 DBI 连接到 bigquery 数据库后列出表的字段

使用 DBI 包从 R 数据帧创建数据并将其插入到 Cloudera Impala

通过DBI从Access到R的连接