我有一个数据集,我需要在其中插入数据(在冲突时替换大约value列).由于这是一个应用程序的瓶颈,我希望这是相当优化的.但在这种情况下,与Sqlite相比,duckdb确实很慢.我到底做错了什么?

MWE

一个简单的例子是这个表,其中idname是唯一的,valcreated_at是应该在冲突中更新的值.

我把桌子摆成这样

con <- DBI::dbConnect(duckdb::duckdb())
q <- "CREATE TABLE main (
  id INTEGER NOT NULL,
  name TEXT NOT NULL,
  val REAL NOT NULL,
  created_at TIMESTAMP NOT NULL,
  UNIQUE(id, name)
)"
DBI::dbExecute(con, q)

然后,我模拟一些值以插入到表中

# Create a data frame
create_data <- function(N, k_letters) {
  data.frame(
    id = sample.int(100, N, replace = TRUE),
    name = replicate(N, paste(sample(letters, k_letters), collapse = "")),
    val = runif(N),
    created_at = Sys.time()
  ) |>
  # to take only unique values...
  dplyr::group_by(id, name) |> dplyr::slice(1) |> dplyr::ungroup()
}

set.seed(123)
df_orig <- create_data(10000, 2)
nrow(df_orig)
#> [1] 9285

DBI::dbAppendTable(con, "main", df_orig)

现在我模拟新数据...

set.seed(124)
new_data <- create_data(100000, 2)
nrow(new_data)
#>[1] 50902

# how many are in the original but remain as they are not in the new data?
anti_join(df_orig, new_data, by = c("id", "name")) |> nrow()
#> [1] 1971

# how many are overwritten by the new?
semi_join(df_orig, new_data, by = c("id", "name")) |> nrow()
#> [1] 7314

# how many are newly added
anti_join(new_data, df_orig, by = c("id", "name")) |> nrow()
#> [1] 43588

...并将其插入到表格中

upsert_db <- function(con, data) {
  q <- "INSERT INTO test
    VALUES ($id, $name, $val, $created_at)
    ON CONFLICT (id, name)
    DO UPDATE SET val = excluded.val, created_at = excluded.created_at;"

  res <- dbSendStatement(con, q)
  dbBind(res, params = unname(as.list(data)))
  rr <- dbGetRowsAffected(res)
  dbClearResult(res)
  rr
}
t0 <- Sys.time()
upsert_db(con, new_data)
#> [1] 50902
difftime(Sys.time(), t0)
#> Time difference of 16.30046 secs

这是可行的,但速度非常慢,使用SQLite编写类似的代码只需不到一秒钟.

我有什么明显的遗漏吗?

推荐答案

对于duckdb,循环插入将比使用批量附加器慢得多.

在这种情况下可以使用大容量操作,方法是先使用要装载到的临时表,然后使用INSERT.

upsert_db <- function(con, data) {
  # create an empty table matching main
  ct <- "CREATE OR REPLACE TEMP TABLE stg as 
  SELECT * FROM main WHERE 1 = 2"

  dbExecute(con, ct)
  dbAppendTable(con, "stg", data)

  # merge the data between the two tables
  iq <- "INSERT INTO main
    select * from stg
    ON CONFLICT (id, name)
    DO UPDATE SET val = excluded.val, created_at = excluded.created_at;"
  rr <- dbExecute(con, iq)

  # drop the source merge table
  dq <- "DROP TABLE stg"
  dbExecute(con, dq)
  rr
}

通过使用临时表并将其加载到dbAppendTable,我们可以为UPSERT操作获得更好的性能.

t0 <- Sys.time()
upsert_db(con, new_data)
#> [1] 50902
difftime(Sys.time(), t0)
#> Time difference of 0.03464794 secs

R相关问答推荐

如何在ggplot 2 geom_segment图表中将UTC转换为EET?

更改Heatmap Annotation对象的名称

在(g)子中使用asserable字符

如何在RMarkdown LaTex PDF输出中包含英语和阿拉伯语?

从R导出全局环境中的所有sf(numrames)对象

如何写一个R函数来旋转最后n分钟?

使用strsplit()将向量操作为数据框

在R中使用download. file().奇怪的URL?

在df中保留原始变量和新变量

R中插入符号训练函数的中心因子和尺度因子预测

plotly hover文本/工具提示在shiny 中不起作用

以相同的方式对每个表进行排序

无法定义沿边轨迹的 colored颜色 渐变(与值无关)

在多页PDF中以特定布局排列的绘图列表不起作用

自定义gggraph,使geom_abline图层仅在沿x轴的特定范围内显示

从多个可选列中选取一个值到一个新列中

使用同一行中的前一个值填充R矩阵中的缺失值

如何在刻面和翻转堆叠条形图中对齐geom_text()

使用一个标签共享多个组图图例符号

如何捕获这个shiny 的、可扩展的react 性用户输入矩阵作为另一个react 性对象,以便进一步操作?