чтение во многих таблицах из баз данных SQLlite и объединение в R - PullRequest
1 голос
/ 08 октября 2019

Я работаю с программой, которая выводит базу данных результатов. У меня есть сотни этих баз данных, которые идентичны по структуре, и я хотел бы объединить их в ОДНУ большую базу данных. В основном меня интересует 1 таблица из каждой базы данных. Я не очень много работаю с базами данных / sql, но это упростило бы другие этапы процесса, чтобы пропустить вывод csv.

Ранее я делал это путем экспорта csv и использовал эти шаги для объединения всех csvs:

Вектор всех CSV и объединение

library(DBI)
library(RSQLite)
library(dplyr)

csv_locs<- list.files(newdir, recursive = TRUE, pattern="*.csv", full.names = TRUE)

pic_dat <- do.call("rbind", lapply(csv_locs, 
FUN=function(files){data.table::fread(files, data.table = FALSE)}))

Как это сделать с таблицами базы данных типа sql ??

Я в основном вытаскиваю первую таблицу, затемприсоединяясь к остальному с помощью петли.

db_locs <- list.files(directory, recursive = TRUE, pattern="*.ddb", full.names = TRUE)


# first table
con1<- DBI::dbConnect(RSQLite::SQLite(), db_locs [1])
start <- tbl(con1, "DataTable")

# open connection to location[i], get table, union, disconnect; repeat. 
for(i in 2:length(db_locs )){
con <- DBI::dbConnect(RSQLite::SQLite(), db_locs[i])
y <- tbl(con, "DataTable")
start <- union(start, y, copy=TRUE)
dbDisconnect(con)
}

Это исключительно медленно! Что ж, если честно, его большие данные и CSV-файл также работают медленно.

Я думаю, что я честно написал самый медленный способ сделать это :) Я не смог заставить работать опцию do.call/lapplyздесь, но, может быть, я что-то упустил.

Ответы [ 2 ]

2 голосов
/ 08 октября 2019

Это похоже на "итеративное rbind использование кадров" , в котором каждый раз, когда вы делаете это union, оно копирует всю таблицу в новый объект (неподтвержденный, но это мойчувство кишки). Это может хорошо работать для некоторых, но очень плохо масштабируется. Я предлагаю вам собрать все таблицы в списке и вызвать data.table::rbindlist один раз в конце, а затем вставить в таблицу.

Без ваших данных я придумаю ситуацию. И поскольку я не совсем уверен, если у вас есть только одна таблица на файл sqlite3, я добавлю две таблицы на базу данных. Если у вас есть только один, решение легко упрощается.

for (i in 1:3) {
  con <- DBI::dbConnect(RSQLite::SQLite(), sprintf("mtcars_%d.sqlite3", i))
  DBI::dbWriteTable(con, "mt1", mtcars[1:3,1:3])
  DBI::dbWriteTable(con, "mt2", mtcars[4:5,4:7])
  DBI::dbDisconnect(con)
}
(lof <- list.files(pattern = "*.sqlite3", full.names = TRUE))
# [1] "./mtcars_1.sqlite3" "./mtcars_2.sqlite3" "./mtcars_3.sqlite3"

Теперь я буду перебирать каждый из них и читать содержимое таблицы

allframes <- lapply(lof, function(fn) {
  con <- DBI::dbConnect(RSQLite::SQLite(), fn)
  mt1 <- tryCatch(DBI::dbReadTable(con, "mt1"),
                  error = function(e) NULL)
  mt2 <- tryCatch(DBI::dbReadTable(con, "mt2"),
                  error = function(e) NULL)
  DBI::dbDisconnect(con)
  list(mt1 = mt1, mt2 = mt2)
})
allframes
# [[1]]
# [[1]]$mt1
#    mpg cyl disp
# 1 21.0   6  160
# 2 21.0   6  160
# 3 22.8   4  108
# [[1]]$mt2
#    hp drat    wt  qsec
# 1 110 3.08 3.215 19.44
# 2 175 3.15 3.440 17.02
# [[2]]
# [[2]]$mt1
#    mpg cyl disp
# 1 21.0   6  160
# 2 21.0   6  160
# 3 22.8   4  108
### ... repeated

Отсюда, просто объедините ихв R и написать в новую базу данных. Хотя вы можете использовать do.call(rbind,...) или dplyr::bind_rows, вы уже упомянули data.table, поэтому я буду придерживаться этого:

con <- DBI::dbConnect(RSQLite::SQLite(), "mtcars_all.sqlite3")
DBI::dbWriteTable(con, "mt1", data.table::rbindlist(lapply(allframes, `[[`, 1)))
DBI::dbWriteTable(con, "mt2", data.table::rbindlist(lapply(allframes, `[[`, 2)))
DBI::dbGetQuery(con, "select count(*) as n from mt1")
#   n
# 1 9
DBI::dbDisconnect(con)

В случае, если вы не можете загрузить их все в R одновременновремя, а затем добавьте их в таблицу в режиме реального времени:

con <- DBI::dbConnect(RSQLite::SQLite(), "mtcars_all2.sqlite3")
for (fn in lof) {
  con2 <- DBI::dbConnect(RSQLite::SQLite(), fn)
  mt1 <- tryCatch(DBI::dbReadTable(con2, "mt1"), error = function(e) NULL)
  if (!is.null(mt1)) DBI::dbWriteTable(con, "mt1", mt1, append = TRUE)
  mt2 <- tryCatch(DBI::dbReadTable(con2, "mt2"), error = function(e) NULL)
  if (!is.null(mt1)) DBI::dbWriteTable(con, "mt2", mt2, append = TRUE)
  DBI::dbDisconnect(con2)
}
DBI::dbGetQuery(con, "select count(*) as n from mt1")
#   n
# 1 9

Это не переносит итеративное замедление, которое вы испытываете.

1 голос
/ 09 октября 2019

Рассмотрим ATTACH для создания схем для баз данных, из которых вы импортируете:

library(DBI)

file1 <- tempfile(fileext = ".sqlite")
file2 <- tempfile(fileext = ".sqlite")

con1 <- dbConnect(RSQLite::SQLite(), dbname = file1)
con2 <- dbConnect(RSQLite::SQLite(), dbname = file2)

dbWriteTable(con1, "iris", iris[1:3, ])
dbWriteTable(con2, "iris", iris[4:6, ])

# Main connection
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbExecute(con, paste0("ATTACH '", file1, "' AS con1"))
#> [1] 0
dbExecute(con, paste0("ATTACH '", file2, "' AS con2"))
#> [1] 0

dbGetQuery(con, "SELECT * FROM con1.iris UNION ALL SELECT * FROM con2.iris")
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1          5.1         3.5          1.4         0.2  setosa
#> 2          4.9         3.0          1.4         0.2  setosa
#> 3          4.7         3.2          1.3         0.2  setosa
#> 4          4.6         3.1          1.5         0.2  setosa
#> 5          5.0         3.6          1.4         0.2  setosa
#> 6          5.4         3.9          1.7         0.4  setosa

Создано в 2019-10-08 пакетом представ. (v0.3.0)

...