You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在DuckDB中以列方式追加数据且不加载至内存

实现DuckDB大表高效追加列(无需全量加载内存)

我有需要存储到磁盘数据库的输入数据,示例数据生成函数如下:

# 示例数据生成函数
incoming_data <- function(ncol=5){
  dat <- sample(1:10,100,replace = T) |> matrix(ncol = ncol) |> as.data.frame()
  random_names <- sapply(1:ncol(dat),\(x) paste0(sample(letters,1), sample(1:100,1)))
  colnames(dat) <- random_names
  dat
}

# 生成示例数据
incoming_data()

实际场景中,单批输入数据包含5k行、约50k列,最终数据总规模达200-400GB。我希望实现类似my_dat <- cbind("my_dat", incoming_data())的横向拼接效果,即在DuckDB的my_dat表中追加新列,且无需将整个表加载至内存。现有初始化代码如下:

# 现有初始化代码
path <- "D:\\R_scripts\\new\\duckdb\\data\\DB.duckdb"
library(duckdb)
con <- dbConnect(duckdb(), dbdir = path, read_only = FALSE)
# 写入初始数据到数据库
dbWriteTable(con, "my_dat", incoming_data())

实现步骤

由于直接加载大表到内存执行cbind不现实,我们可以利用DuckDB的磁盘级操作特性,通过临时表中转+分步骤增列更新的方式实现需求,核心是避免全量加载原表。

1. 初始化表时添加行号主键

首先修改初始化代码,给表添加row_id列作为行唯一标识,确保后续新列能准确对应到每一行:

path <- "D:\\R_scripts\\new\\duckdb\\data\\DB.duckdb"
library(duckdb)
con <- dbConnect(duckdb(), dbdir = path, read_only = FALSE)

# 生成初始数据并添加行号
initial_dat <- incoming_data()
initial_dat$row_id <- seq_len(nrow(initial_dat))

# 写入数据库,并设置行号为主键(加速后续关联)
dbWriteTable(con, "my_dat", initial_dat)
dbExecute(con, "ALTER TABLE my_dat ADD PRIMARY KEY (row_id)")

2. 批量追加新列的通用流程

每次获取新批次数据后,按以下步骤操作:

# 1. 生成新批次数据并添加行号(必须和原表行数完全匹配)
new_dat <- incoming_data()
new_dat$row_id <- seq_len(nrow(new_dat))

# 2. 将新数据写入临时表(仅加载新批次数据到内存,原表无需动)
dbWriteTable(con, "temp_new_cols", new_dat, temporary = TRUE)

# 3. 获取新表中除row_id外的所有列名
new_cols <- dbGetQuery(con, "SELECT column_name FROM information_schema.columns WHERE table_name = 'temp_new_cols' AND column_name != 'row_id'")$column_name

# 4. 给目标表逐个添加新列(自动匹配数据类型)
for (col in new_cols) {
  col_type <- dbGetQuery(con, sprintf("SELECT data_type FROM information_schema.columns WHERE table_name = 'temp_new_cols' AND column_name = '%s'", col))$data_type
  dbExecute(con, sprintf("ALTER TABLE my_dat ADD COLUMN %s %s", col, col_type))
}

# 5. 通过行号关联,将临时表的列值更新到目标表
update_queries <- lapply(new_cols, function(col) {
  sprintf("UPDATE my_dat SET %s = temp_new_cols.%s FROM temp_new_cols WHERE my_dat.row_id = temp_new_cols.row_id", col, col)
})
lapply(update_queries, function(q) dbExecute(con, q))

# 6. 删除临时表释放资源
dbExecute(con, "DROP TABLE temp_new_cols")

关键说明

  • 全程无需加载原表到内存,所有操作由DuckDB在磁盘上高效执行,适合超大规模数据
  • row_id是核心关联键,必须保证每次新批次数据的行数和行号与原表完全一致
  • 临时表自动在会话结束后销毁,也可手动删除释放空间
  • 分步骤增列和更新,避免一次性操作大表带来的性能压力

内容的提问来源于stack exchange,提问作者mr.T

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 04:57:44