在通过 ODBC 连接到数据库的 R 中,如何在 运行 一个查询后断开连接以发送另一个查询而不取消前一个查询?

In R connected to a database through ODBC, how do I disconnect after running one query to send another query without cancelling the previous?

我是 R 的新手,也是 Stack Overflow 的新手,如果我 post 不正确,我深表歉意!我使用 ODBC 和 DBI 包将 R 链接到 Access 数据库。我搜索了互联网,但找不到编写更新查询的方法,因此我创建了一个循环,该循环几乎可以手动执行相同的操作。问题是要做到这一点,我正在使用 dbSendStatement 函数。似乎当您使用该函数时,您需要一种方法在移动到另一个查询之前结束它,否则您会收到此错误:

In new_result(connection@ptr, statement, immediate) :
  Cancelling previous query

如何完成一个查询,以便在不取消最后一个查询的情况下继续下一个查询?

这是我认为相关的代码部分:

require(DBI)
require (odbc)

dB.Connection = dbConnect(odbc::odbc(), dsn = "Development Database")

BLDG.LVL.Details = data.frame()

for (i in 2:length(Temp.File.List)){
  Temp.BLDG.LVL = read_excel(Temp.File.List[i], range = cell_cols("A:O"))
  BLDG.LVL.Details = rbind(BLDG.LVL.Details, Temp.BLDG.LVL)
  file.move(Temp.File.List[i],paste("C:/UserData/",Sys.getenv("USERNAME"),"/OneDrive - Siemens AG/Development Database/06 FIM (BLDG LVL)/Archive", sep = ""))}

for (i in 1:nrow(BLDG.LVL.Details)){
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Included = ",BLDG.LVL.Details[i,3]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Proposed_kW_Yr1 = ",BLDG.LVL.Details[i,4]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Proposed_kWh_Yr1 = ",BLDG.LVL.Details[i,6]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Proposed_NGas_Yr1 = ",BLDG.LVL.Details[i,7]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Proposed_H2O_Yr1 = ",BLDG.LVL.Details[i,8]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Proposed_FuelOil_Yr1 = ",BLDG.LVL.Details[i,9]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Proposed_Propane_Yr1 = ",BLDG.LVL.Details[i,10]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Proposed_Other_Yr1 = ",BLDG.LVL.Details[i,11]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Operational_Savings_USD_BLDG = ",BLDG.LVL.Details[i,13]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Operational_Savings_Yrs_BLDG = ",BLDG.LVL.Details[i,14]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Operational_Savings_Persistence_BLDG = ",BLDG.LVL.Details[i,15]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbSendStatement(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Included = ",BLDG.LVL.Details[i,3]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))}


dbSendStatement() returns一个结果对象,需要用dbClearResult()清除。使用 dbExecute() 发送查询更容易,基本上这是 dbSendStatement()dbClearResult().

的组合
for (i in 1:nrow(BLDG.LVL.Details)){
  DBI::dbExecute(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Included = ",BLDG.LVL.Details[i,3]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))
  DBI::dbExecute(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Proposed_kW_Yr1 = ",BLDG.LVL.Details[i,4]," WHERE FIM_ID = ",BLDG.LVL.Details[i,1]," AND BUILDING_ID = ",BLDG.LVL.Details[i,2],";", sep = ""))

有几种方法可以改善这一点:

  • 使用dbQuoteLiteral()引用字面值,防止SQL注入,这也解决了In R connected to an Access Database through ODBC, how can I update a text field?:

    for (i in 1:nrow(BLDG.LVL.Details)){
      DBI::dbExecute(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Included = ",DBI::dbQuoteLiteral(conn,BLDG.LVL.Details[i,3])," WHERE FIM_ID = ",DBI::dbQuoteLiteral(conn,BLDG.LVL.Details[i,1])," AND BUILDING_ID = ",DBI::dbQuoteLiteral(conn,BLDG.LVL.Details[i,2]),";", sep = ""))
      DBI::dbExecute(conn = dB.Connection, statement = paste("UPDATE DC_FIMs_BLDG_Lvl SET Proposed_kW_Yr1 = ",DBI::dbQuoteLiteral(conn,BLDG.LVL.Details[i,4])," WHERE FIM_ID = ",DBI::dbQuoteLiteral(conn,BLDG.LVL.Details[i,1])," AND BUILDING_ID = ",DBI::dbQuoteLiteral(conn,BLDG.LVL.Details[i,2]),";", sep = ""))
    
  • 使用参数安全地传递查询参数(遗憾的是,占位符语法因 DBMS 而异,? 可能会起作用):

    for (i in 1:nrow(BLDG.LVL.Details)){
      DBI::dbExecute(conn = dB.Connection, statement = "UPDATE DC_FIMs_BLDG_Lvl SET Included = ? WHERE FIM_ID = ? AND BUILDING_ID = ?;", params = list(BLDG.LVL.Details[i,3],BLDG.LVL.Details[i,1],BLDG.LVL.Details[i,2]))
      ...
    
  • 冒着明显的风险,在同一查询中更新多个列会快得多,有或没有占位符:

    for (i in 1:nrow(BLDG.LVL.Details)){
      DBI::dbExecute(conn = dB.Connection, statement = "UPDATE DC_FIMs_BLDG_Lvl SET Included = ?, Proposed_kW_Yr1 = ? WHERE FIM_ID = ? AND BUILDING_ID = ?;", params = list(BLDG.LVL.Details[i,3],BLDG.LVL.Details[i,4],BLDG.LVL.Details[i,1],BLDG.LVL.Details[i,2]))
      ...
    
  • 我相信您可以用以下形式替换整个循环:

    library(dm)
    
    ...
    
    tbl <- dplyr::tbl(conn, "DC_FIMs_BLDG_Lvl")
    dplyr::rows_update(tbl, BLDG.LVL.Details, by = c("FIM_ID", "BUILDING_ID"), in_place = TRUE, copy = TRUE)
    

    该功能需要加载 dm 包。在 https://krlmlr.github.io/dm/articles/howto-dm-rows.html 阅读更多内容——该文本显示了一种不同的方法,如果您正在访问数据库中的多个表,该方法很有用。让我知道它是否适合你。

  • 看看 dbx package 发送更新查询,它对你有用吗?