使用数据框和dbSendQuery更新表

问题描述 投票:0回答:1

我已经在数据库中查询了一个表,将值存储在数据框中,然后对其进行了操作。这是我用于查询数据的代码:

#Setup Connection
con1 <- dbConnect(odbc::odbc(), "XXXX", database="XXXX")
r1 <- dbSendQuery(con1, "
select pcd, oseast1m, osnrth1m from onspd as ons where ons.pcd like 'bt%' and oseast1m != ''
")
result <- dbFetch(r1)

我现在想用类似的方法将值从数据帧写回到数据库中:

dbClearResult(r1)
sql <- "
update ons
set ons.oseast1m=?east, os.osnrth1m=?west
from ONS_TEST as ons where ons.Postcode=?post
"
r1 <- dbSendQuery(con1, sqlInterpolate(ANSI(), sql, east = result$oseast1m, west = result$osnrth1m, post = result$pcd))

这给我一个错误,“值必须为长度1”,这显然与我想要的不正确。

运行更新的语法是什么?还是我需要编写一个for循环来实现相同的目的?

谢谢

麦克

sql r
1个回答
0
投票

我认为您有两个选择:(1)UPSERT或(2)参数化查询。

第一个优点是速度(通常取决于DBMS)的速度,但要以特定于DBMS的SQL方言为代价,并且有点复杂。第二个优点是简单,但是如果您有很多行,则可能需要更长的时间。

1。 UPSERT

步骤:创建一个临时表(请参阅注释);上传数据;用冲突解决方法进行更新操作。

我在这里使用temp_table_997作为临时表,但是有许多方法可以处理临时表,以免您不小心将其遗留在表中。我发现,DBMS的成功与众不同,因此,请留给读者。

DBI::dbExecute(con, "
  CREATE TABLE temp_table_997 AS
  SELECT oseast1m, osnrth1m, Postcode FROM ons LIMIT 0")                         # [1,2]
DBI::dbWriteTable(con, "temp_table_997", result[,c("east", "west", "postcode")]) # [3,4]
DBI::dbExecute(con, "
  INSERT INTO ons (oseast1m, osnrth1m)
    SELECT oseast1m, osnrth1m
    FROM temp_table_997
  ON CONFLICT ( Postcode ) DO
    UPDATE SET oseast1m=EXCLUDED.oseast1m, osnrth1m=EXCLUDED.osnrth1m
")                                                                               # [5]

注意:

  1. 采用此技术的其他答案/文章可能会使用select * ...,尽管最佳实践不建议这样做。通常最好在表中只显示必要的字段。

  2. 我使用create table ... as select ...,以便保留列类型。特别是对于所有各种类型的数字(浮点数,整数,bigint,smallint,甚至“位”)和其他字段……以及R并未达到这种粒度级别的事实,我发现最好是明确的上传数据时。使用此技术可确保目标表中使用的类型是实际使用的类型。在某些DBMS中可能不是必需的,但我认为这没有什么害处。

  3. 与注释1类似,您可能应该仅上传所需的列,包括标识字段和具有更新的字段;如果存在永不更新的字段,则没有理由浪费带宽,并且在较大的数据集上,这可能会对上传时间产生相当大的影响。 (例如results[,c("Postcode",...)])。

  4. 尽管我使用的工具和数据库足够聪明,可以按顺序处理列,但我不知道所有DBMS都是这种情况,因此,最好且容易地保持顺序列相同。

  5. 我推断出Postcode在表中是唯一的。它不一定是表中的键(这是一个单独的讨论),但是假设该字段唯一地标识行。如果不是这样,则以上查询可能会影响比预期更多的行。

这在SQLite和Postgres上(对我而言)有效,但是其他DBMS的说法可能相同或非常相似。

对于SQL Server,您需要一个稍微不同的查询。 (由于SQL Server的方言,请意识到上面的CREATE ... SELECT ... LIMIT 0必须为CREATE ... SELECT TOP 0 ...。)

DBI::dbExecute(con, "
  DECLARE @dummy int;
  MERGE ons WITH (HOLDLOCK) as tgt
  USING (SELECT ... FROM temp_table_997) as src
    ON ( tgt.Postcode )
  WHEN MATCHED THEN UPDATE SET tgt.oseast1m=src.oseast1m, tgt.osnrth1m=src.osnrth1m
  WHEN NOT MATCHED THEN INSERT (oseast1m, oseast1m) values (src.oseast1m, src.oseast1m)
")

如果您使用的是此方法,请不要忘记清理:

DBI::dbExecute(con, "drop table temp_table_997")

2。绑定(参数化查询)

[如果只有几行,或者您真的没有看到这样做会浪费时间,请尝试一下。

res <- DBI::dbSendStatement(con, "
  UPDATE ons
  SET ons.oseast1m=?, ons.osnrth1m=?
  WHERE ons.Postcode=?")
DBI::dbBind(res, result[,c("east", "west", "postcode")]) # order and number of columns is important
DBI::dbGetRowsAffected(res)

指示参数的方法(上面的?)仅取决于DBMS,而不取决于DBIodbc包;您需要找到适合您的方法。这可能是??name$name:name;也许还有其他人。

((我承认这可能同样有效。几年前,我尝试了几种方法,无论是由于使用的驱动程序还是DBI的一个版本,甚至是我对事情的误解……都可能就像upsert一样有效。我现在不打算对其进行测试,因为差异可能仅与较大的数据集有关。YMMV。)

热门问题
推荐问题
最新问题