Skip to content
AgileLogics
7 min read

Working with Databases

Prerequisites: Joining data, Error handling and defensive code

Much real-world data lives in databases. R connects to them through DBI, a common interface with a driver package for each database: RSQLite, RPostgres, RMariaDB, odbc (SQL Server and many others), duckdb, and more.

Connecting and querying

This example uses SQLite, which stores a whole database in one file (or, here, in memory) and needs no server:

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ":memory:")

dbWriteTable(con, "orders", data.frame(
  id = 1:4,
  customer = c("Ada", "Linus", "Ada", "Grace"),
  total = c(120, 45, 80, 300)
))

dbListTables(con)
dbGetQuery(con, "SELECT customer, SUM(total) AS spent
                 FROM orders
                 GROUP BY customer
                 ORDER BY spent DESC")
[1] "orders"
  customer spent
1    Grace   300
2      Ada   200
3    Linus    45

dbGetQuery runs SQL and returns a data frame. (If you're new to SQL, the SQL course covers it from the ground up.)

Always use parameters for values

Never paste user-supplied values into SQL text — that allows SQL injection. Pass them as parameters instead:

library(DBI)

con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "orders", data.frame(customer = c("Ada", "Linus"), total = c(120, 45)))

min_total <- 50
dbGetQuery(con, "SELECT * FROM orders WHERE total > ?", params = list(min_total))
dbDisconnect(con)
  customer total
1      Ada   120

The ? placeholder is filled in safely by the driver.

dplyr on databases: dbplyr

With the dbplyr package, dplyr code runs inside the database — dplyr translates it to SQL, so large tables never have to be loaded into R:

library(dplyr)

orders_db <- tbl(con, "orders")

orders_db |>
  summarise(spent = sum(total), .by = customer) |>
  arrange(desc(spent)) |>
  show_query()     # see the generated SQL

result <- orders_db |> filter(total > 100) |> collect()   # bring results into R

collect() runs the query and returns a tibble — do the heavy filtering and aggregation in the database first, then collect only what you need.

Credentials

Don't write passwords in scripts. Read them from environment variables:

con <- dbConnect(RPostgres::Postgres(),
  host = Sys.getenv("DB_HOST"),
  dbname = "sales",
  user = Sys.getenv("DB_USER"),
  password = Sys.getenv("DB_PASSWORD")
)

Always call dbDisconnect(con) when you're done.

Common mistakes

  • Pulling a huge table into R with SELECT * and then filtering. Filter and aggregate in the database.
  • Building SQL with paste() and user input — use parameters.