去年了解到 DuckDB 的存在,因为没有适当的场景需要,便也没有专门学习。最近有了需要存储、快速使用一些较大数据的场景,趁机学习。

目前 DuckDB 吸引我的有两点:

  1. 没有外部依赖,直接安装 duckdb 包即可使用。
  2. 可以处理超过内存的数据。

使用方法一:DuckDB + SQL

这种方法相对简单,把数据存入 DuckDB 数据库后,用 SQL 查询即可。

创建 DuckDB 数据库

  1. # install.packages("duckdb")
  2. library(duckdb)
  3. # 创建 DuckDB 连接,同时会在本地创建 local.duckdb 文件
  4. con = dbConnect(duckdb(), dbdir = "local.duckdb")

向 DuckDB 数据库写入表

可以将 R 中的数据框写入到 DuckDB 数据库中:

  1. # 写入 R 中的数据框
  2. dbWriteTable(con, "table_iris", iris)
  3. dbWriteTable(con, "table_mtcars", mtcars)
  4. dbListTables(con)
  5. #> [1] "table_iris" "table_mtcars"

也可以将本地的文件直接导入到 DuckDB 数据库中,这个方法可以突破内存大小的限制:

  1. # 创建本地 CSV 文件
  2. data.frame(
  3. Species = c("setosa", "versicolor", "virginica"),
  4. code = LETTERS[1:3]
  5. ) |> write.csv("species_code.csv", row.names = FALSE)
  6. # 将本地 CSV 文件直接导入 DuckDB
  7. duckdb_read_csv(con, "table_code", "species_code.csv")
  8. dbListTables(con)
  9. #> [1] "table_code" "table_iris" "table_mtcars"

从 DuckDB 数据库删除表

  1. dbRemoveTable(con, "table_mtcars")
  2. dbListTables(con)
  3. #> [1] "table_code" "table_iris"

使用 SQL 查询 DuckDB 数据库

  1. dbGetQuery(con, "SELECT * FROM table_iris LIMIT 3;")
  2. #> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
  3. #> 1 5.1 3.5 1.4 0.2 setosa
  4. #> 2 4.9 3.0 1.4 0.2 setosa
  5. #> 3 4.7 3.2 1.3 0.2 setosa
  1. dbGetQuery(con, "SELECT Species, COUNT(*) AS n
  2. FROM table_iris
  3. GROUP BY Species
  4. ORDER BY n;")
  5. #> Species n
  6. #> 1 setosa 50
  7. #> 2 versicolor 50
  8. #> 3 virginica 50
  1. # 表连接
  2. dbGetQuery(con, "SELECT a.Species, code, n FROM
  3. (
  4. SELECT Species, COUNT(*) AS n
  5. FROM table_iris
  6. GROUP BY Species
  7. ) a
  8. LEFT JOIN table_code b
  9. ON a.Species = b.Species;")
  10. #> Species code n
  11. #> 1 setosa A 50
  12. #> 2 versicolor B 50
  13. #> 3 virginica C 50

断开 DuckDB 连接

  1. # 断开数据库连接
  2. dbDisconnect(con)

使用方法二:duckplyr

  1. # install.packages("duckplyr")
  2. library(duckplyr)
  3. library(dplyr)
  4. con = dbConnect(duckdb(), dbdir = "local.duckdb")

从 DuckDB 创建数据源

使用 tbl() 连接 DuckDB 数据库中的表,可以看到表的行数是 ??,因为 duckplyr 采用惰性计算,只是生成了查询计划,并没有实际执行。

  1. table_iris = tbl(con, "table_iris")
  2. table_iris
  3. #> # Source: table<table_iris> [?? x 5]
  4. #> # Database: DuckDB v0.10.2 [unknown@Linux 5.15.0-107-generic:R 4.4.0//home/shitao/git/shitao-blog/content/posts/2024-05-27-learn-duckdb/local.duckdb]
  5. #> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
  6. #> <dbl> <dbl> <dbl> <dbl> <fct>
  7. #> 1 5.1 3.5 1.4 0.2 setosa
  8. #> 2 4.9 3 1.4 0.2 setosa
  9. #> 3 4.7 3.2 1.3 0.2 setosa
  10. #> 4 4.6 3.1 1.5 0.2 setosa
  11. #> 5 5 3.6 1.4 0.2 setosa
  12. #> 6 5.4 3.9 1.7 0.4 setosa
  13. #> 7 4.6 3.4 1.4 0.3 setosa
  14. #> 8 5 3.4 1.5 0.2 setosa
  15. #> 9 4.4 2.9 1.4 0.2 setosa
  16. #> 10 4.9 3.1 1.5 0.1 setosa
  17. #> # ℹ more rows

使用管道操作数据

现在就可以和平常的数据一样进行分析了,要得到最终执行结果,需在管道最后接上 collect()

  1. table_iris |>
  2. count(Species) |>
  3. collect()
  4. #> # A tibble: 3 × 2
  5. #> Species n
  6. #> <fct> <dbl>
  7. #> 1 setosa 50
  8. #> 2 versicolor 50
  9. #> 3 virginica 50
  1. table_code = tbl(con, "table_code")
  2. table_iris |>
  3. count(Species) |>
  4. left_join(table_code, join_by(Species)) |>
  5. select(Species, code, n) |>
  6. collect()
  7. #> # A tibble: 3 × 3
  8. #> Species code n
  9. #> <fct> <chr> <dbl>
  10. #> 1 setosa A 50
  11. #> 2 versicolor B 50
  12. #> 3 virginica C 50

断开 DuckDB 连接

数据库用完,记得断开连接哦!

  1. # 断开数据库连接
  2. dbDisconnect(con)