更新 SQLite 数据库而不在本地下载数据

Update SQLite database without downloading data locally

我有以下 2 个数据帧:

library(tidyverse)
library(RSQLite)

df1 <- data.frame(user_id=c("A","B","C"), 
                  transaction_date=c("2019-01-01","2019-01-01","2019-01-01"))

df2 <- data.frame(user_id=c("C","D","E"), 
                  transaction_date=c("2019-01-03","2019-01-03","2019-01-03"))

df1
df2

#    user_id    transaction_date
#    <fct>      <fct>
#    A          2019-01-01
#    B          2019-01-01
#    C          2019-01-01

#    user_id    transaction_date
#    <fct>      <fct>
#    C          2019-01-03
#    D          2019-01-03
#    E          2019-01-03

我想找到每个 user_id 的最短交易日期。我可以这样做:

rbind(df1, df2) %>%
group_by(user_id) %>%
summarise(min_dt=min(transaction_date %>% as.Date()))

#    user_id    min_dt
#    <fct>      <date>
#    A          2019-01-01
#    B          2019-01-01
#    C          2019-01-01
#    D          2019-01-03
#    E          2019-01-03

问题是我有 100 个数据帧(每天 1 个),每个数据帧有数百万行。每次我引入新的 user_id 并计算 min_dt 时,user_id 的列表都会增加。所以整个过程随着时间的推移变得非常缓慢。 问题:1) 运行 SQLite 中的计算会更快吗? 2) 如果是这样,我如何在不每次都在本地下载数据的情况下完成此操作?

这是我试过的方法。

第 1 步:从 df1 创建数据库:

db <- dbConnect(SQLite(), dbname = "user_db.sqlite")

dbWriteTable(conn = db, name = "first_appeared", value = df1, append=TRUE)
tbl(db, "first_appeared")

##  Source:   table<first_appeared> [?? x 2]
##  Database: sqlite 3.29.0 [user_db.sqlite]
#   user_id transaction_date
#   <chr>   <chr>           
# 1 A       2019-01-01      
# 2 B       2019-01-01      
# 3 C       2019-01-01 

第 2 步:追加 df2:

dbWriteTable(conn = db, name = "first_appeared", value = df2, append=TRUE)
tbl(db, "first_appeared")

##  Source:   table<first_appeared> [?? x 2]
##  Database: sqlite 3.29.0 [/Volumes/GoogleDrive/My Drive/Ad hoc/201908 v2
#   mapper/user_db.sqlite]
#   user_id transaction_date
#   <chr>   <chr>           
# 1 A       2019-01-01      
# 2 B       2019-01-01      
# 3 C       2019-01-01      
# 4 C       2019-01-03      
# 5 D       2019-01-03      
# 6 E       2019-01-03 

第 3 步:在 SQLite

中计算 min_dt
tbl(db, "first_appeared") %>%
group_by(user_id) %>%
summarise(first_appeared=min(transaction_date))

dbDisconnect(db)            # Close connection

##  Source:   lazy query [?? x 2]
##  Database: sqlite 3.29.0 [/Volumes/GoogleDrive/My Drive/Ad hoc/201908 v2
##  mapper/user_db.sqlite]
#   user_id first_appeared
#   <chr>   <chr>         
# 1 A       2019-01-01    
# 2 B       2019-01-01    
# 3 C       2019-01-01    
# 4 D       2019-01-03    
# 5 E       2019-01-03  

第 4 步:如何在不先将数据下载到本地的情况下将这些结果直接传输到数据库(覆盖数据库)?

一般方法

让我从我会使用的一般方法开始:在每个新的一天更新 'latest' table。

update = function(existing_table, new_day_table){
  new_existing_table = existing_table %>%
    full_join(new_day_table, by = "user_id", suffix = c("_exist","_new") %>%
    mutate(transaction_date = ifelse(test = !is.na(transaction_date_exist)
                                            & (is.na(transaction_date_new)
                                               | transaction_date_exist < transaction_date_new ), 
                                     yes = transaction_date_exist,
                                     no = transaction_date_new)) %>%
    select(user_id, transaction_date)
}

在 R 中,您每天都会 运行 这个函数:

existing_table = update(existing_table, next_day_table)

我推荐这种方法,因为在每次计算时你只需要两个 tables:你存储所有细节的 table 和你用来更新的 table它。这比每次更新的所有每日数据文件要处理的数据要少得多。

假设您想在 SQLite 中执行此操作?

我上面的更新函数中的代码应该可以通过 dbplyr 从 R 轻松转换为 SQLite。假设 existing_tablenext_day_table 都已经在 SQLite.

但是 dbplyr 不会将生成的 table 保存为对象。因此,如果您调用 new_table = update(existing_table, next_day_table),那么 new_table 将由 dbplyr 用于构造它的 SQL 命令定义。

要将其另存为 table,您可能需要如下内容:

sql_query = paste("CREATE TABLE new_first_appeared AS\n"
                  ,as.character(sql_render(new_table))
)
dbExecute(db_connection, sql_query)

请注意,您必须在数据库中写入 new_first_appeared。您不能直接覆盖 first_appeared,因为 new_first_appeared 的定义取决于 first_appeared。然后,您必须删除现有的 table first_appeared,并重命名 new_first_appeared.

您还能考虑什么?

根据您的上下文,您 first_appeared table 中的现有记录一旦创建可能不会更改。如果是这种情况,那么与其重写整个 table,不如考虑只识别新记录并将它们附加到现有的 table.

为此,您可能需要 SQLite 中的 INSERT INTO first_appeared_table SELECT * FROM table 模式。您还需要将查询更改为 return 仅新记录。