Quick summary
Summarize this blog with AI
Introduction
To join every element of a vector into one string, use paste0(x, collapse = ""). Without collapse, paste0 combines corresponding elements and returns a vector. It removes the separator between arguments; it does not remove spaces already present in the input. The examples also preserve the important boundary between formatting text and binding SQL values.
Tested with R 4.5.1, DBI 1.3.0 and RSQLite 3.53.3. Run the code blocks in order. Install the named packages before starting; inputs are supplied below.
Distinguish elementwise joins from collapse
Start with a character vector. The three calls below answer different questions.
parts <- c("Data", " ", "Science")
print(paste0(parts, collapse = "")) # "Data Science"
print(paste0(c("A", "B"), 1:2)) # "A1" "B2"
print(paste(c("A", "B"), 1:2)) # "A 1" "B 2"
stopifnot(identical(paste0(parts, collapse = ""), "Data Science"))
stopifnot(identical(paste0(c("A", "B"), 1:2, collapse = ","), "A1,B2"))
Collapse is applied after corresponding arguments are joined. R documents this order and the conversion to character.
Make missing and empty input policies explicit
Missing values become the literal text "NA". A zero-length input normally behaves like an empty string when paired with another argument; recycle0 changes that behavior.
stopifnot(identical(paste0("id-", NA), "id-NA"))
stopifnot(identical(paste0(character(0), collapse = ""), ""))
stopifnot(identical(paste0("id-", character(0)), "id-"))
stopifnot(identical(paste0("id-", character(0), recycle0 = TRUE), character(0)))
names <- c("Ada", NA_character_, "Lin")
labels <- ifelse(is.na(names), NA_character_, paste0("Hello, ", names))
stopifnot(identical(labels, c("Hello, Ada", NA_character_, "Hello, Lin")))
print(labels)
Use a deliberate replacement or preserve missingness; do not mistake the text "NA" for a missing value. Dropping missing entries before joining can change their alignment with another vector.
Check lengths and identifier collisions
R recycles shorter arguments, even when their lengths are not multiples. Concatenation alone also does not guarantee a unique key.
stopifnot(identical(paste0(c("A", "B"), 1:3), c("A1", "B2", "A3")))
people <- data.frame(first = c("Ada", "Lin"), last = c("Lovelace", "Chen"))
people$label <- paste(people$first, people$last)
stopifnot(identical(people$label, c("Ada Lovelace", "Lin Chen")))
parts_by_record <- list(c("A", "B"), character(0), c("C", "D", "E"))
joined <- vapply(parts_by_record, paste0, character(1), collapse = "")
stopifnot(identical(joined, c("AB", "", "CDE")))
keys <- paste0(c("ab", "a"), c("c", "bc"))
stopifnot(anyDuplicated(keys) == 2L) # both become "abc"
Use equal-length columns for row labels. Keep structured key columns when joining tables, or use a defined collision-resistant encoding rather than assuming a separator-free label is an identifier.
Keep SQL values separate from SQL syntax
This complete DBI/RSQLite example uses an in-memory database. Its question-mark placeholders bind values, including apostrophes and SQL-looking text. The connection closes even when the function errors.
demo_insert <- function(df) {
con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
on.exit(DBI::dbDisconnect(con), add = TRUE)
DBI::dbExecute(con, "CREATE TABLE users (name TEXT, age INTEGER)")
if (nrow(df) > 0L) {
DBI::dbExecute(con, "INSERT INTO users (name, age) VALUES (?, ?)",
params = list(df$name, df$age))
}
result <- DBI::dbGetQuery(con, "SELECT name, age FROM users ORDER BY rowid")
stopifnot(DBI::dbExistsTable(con, "users"))
result
}
users <- data.frame(name = c("Jane", "O'Reilly", "Robert'); DROP TABLE users; --",
"", "Zoƫ", NA_character_, "line\nbreak"),
age = c(25L, 30L, 35L, 0L, 22L, NA_integer_, 40L))
result <- demo_insert(users)
stopifnot(identical(result$name, users$name), identical(result$age, users$age))
stopifnot(nrow(demo_insert(users[FALSE, ])) == 0L)
cat(nrow(result), "rows round-tripped; table preserved\n")
# 7 rows round-tripped; table preserved
Do not quote placeholders or manually escape values. Placeholder syntax is driver-specific. Parameters represent values, not identifiers; keep table and column names fixed, or select allowed identifiers and use DBI::dbQuoteIdentifier(). DBI explains parameterized queries.