Consultas parametrizadas y transacciones
Pase parámetros de forma segura y gestione transacciones de base de datos de varios pasos en R.
Consultas parametrizadas y transacciones es una lección gratuita de R Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de R Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de R Academy incluye 4 lecciones en total.
¿Por qué usar consultas parametrizadas?
Cuando la entrada del usuario se inserta directamente en una cadena SQL, un atacante puede inyectar código SQL malicioso. Las consultas parametrizadas separan la estructura SQL de los valores de los datos, lo que hace imposibles los ataques de inyección y, además, produce un código más limpio y fácil de leer.
library(DBI)
# DANGEROUS: string concatenation (SQL injection risk!)
user_input <- "'; DROP TABLE users; --"
# bad_sql <- paste0("SELECT * FROM users WHERE name = '", user_input, "'")
# dbGetQuery(con, bad_sql) <- NEVER do this
# SAFE: parameterized query
# dbGetQuery(con, 'SELECT * FROM users WHERE name = $1',
# params = list(user_input))
cat('Parameterized queries prevent SQL injection')dbGetQuery() con parámetros
dbGetQuery() ejecuta una instrucción SELECT y devuelve los resultados como un marco de datos. Pase una lista params para vincular valores a los marcadores de posición. La sintaxis de los marcadores varía según el controlador: $1 para PostgreSQL y ? para SQLite/MySQL.
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbWriteTable(con, 'employees', data.frame(
name = c('Alice', 'Bob', 'Carol'),
department = c('Engineering', 'Marketing', 'Engineering'),
salary = c(90000, 75000, 95000)
))
# Parameterized SELECT — SQLite uses ?
result <- dbGetQuery(
con,
'SELECT name, salary FROM employees WHERE department = ?',
params = list('Engineering')
)
print(result)
dbDisconnect(con)dbExecute() — INSERT, UPDATE, DELETE
dbExecute() ejecuta instrucciones SQL que modifican datos (INSERT, UPDATE, DELETE) y devuelve el número de filas afectadas. Use params para vincular valores de forma segura. Esta es la función adecuada para las operaciones de escritura.
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbWriteTable(con, 'users', data.frame(
id = 1:3,
name = c('Alice', 'Bob', 'Carol'),
active = c(TRUE, FALSE, TRUE)
))
# Safe UPDATE with parameters
rows_affected <- dbExecute(
con,
'UPDATE users SET active = ? WHERE id = ?',
params = list(TRUE, 2)
)
cat('Rows updated:', rows_affected, '\n')
# Verify
result <- dbGetQuery(con, 'SELECT * FROM users WHERE id = 2')
cat('Bob active:', result$active)
dbDisconnect(con)Varios parámetros en una consulta
Puede vincular varios parámetros incluyéndolos todos en la lista params. Se vinculan en orden a los marcadores de posición (? o $1, $2, ...) de la cadena SQL. Haga coincidir siempre el número de elementos de la lista con el número de marcadores de posición.
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbWriteTable(con, 'products', data.frame(
id = 1:4,
name = c('Apple', 'Banana', 'Cherry', 'Date'),
price = c(1.5, 0.8, 3.0, 5.0),
stock = c(100, 200, 50, 30)
))
# Filter by two parameters
result <- dbGetQuery(
con,
'SELECT name, price FROM products WHERE price > ? AND stock > ?',
params = list(1.0, 40)
)
print(result)
dbDisconnect(con)INSERT parametrizado
Las instrucciones INSERT parametrizadas agregan registros nuevos de forma segura. Vincule como parámetro el valor de cada columna. Para inserciones masivas, use dbAppendTable() con un marco de datos; es más rápido y DBI gestiona la vinculación automáticamente.
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE logs (level TEXT, message TEXT, ts TEXT)')
# Parameterized INSERT
dbExecute(
con,
'INSERT INTO logs (level, message, ts) VALUES (?, ?, ?)',
params = list('INFO', 'User logged in', as.character(Sys.time()))
)
result <- dbGetQuery(con, 'SELECT * FROM logs')
print(result)
dbDisconnect(con)dbBegin() y dbCommit() — Transacciones
Una transacción agrupa varias instrucciones SQL en una única unidad atómica: todas se completan correctamente o no se aplica ninguna. Use dbBegin() para iniciarla y, después, dbCommit() para confirmar todos los cambios conjuntamente.
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE accounts (id INTEGER, balance REAL)')
dbExecute(con, "INSERT INTO accounts VALUES (1, 1000), (2, 500)")
# Transfer 200 from account 1 to account 2
dbBegin(con)
dbExecute(con, 'UPDATE accounts SET balance = balance - ? WHERE id = ?',
params = list(200, 1))
dbExecute(con, 'UPDATE accounts SET balance = balance + ? WHERE id = ?',
params = list(200, 2))
dbCommit(con)
result <- dbGetQuery(con, 'SELECT * FROM accounts')
print(result)
dbDisconnect(con)dbRollback() — Reversión de una transacción
Si falla alguna instrucción de una transacción, llame a dbRollback() para deshacer todos los cambios realizados desde dbBegin(). Esto mantiene la base de datos en un estado coherente. Combine siempre dbBegin() con dbCommit() o dbRollback().
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE orders (id INTEGER, amount REAL)')
dbExecute(con, 'INSERT INTO orders VALUES (1, 500)')
# Simulate a failed transaction
dbBegin(con)
tryCatch({
dbExecute(con, 'UPDATE orders SET amount = ? WHERE id = ?',
params = list(600, 1))
stop('Simulated error during processing') # something goes wrong
dbCommit(con)
}, error = function(e) {
dbRollback(con)
cat('Transaction rolled back:', e$message, '\n')
})
# Amount is still 500 (rollback worked)
result <- dbGetQuery(con, 'SELECT * FROM orders')
cat('Amount after rollback:', result$amount)
dbDisconnect(con)dbWithTransaction() — Patrón más seguro
dbWithTransaction() incluye automáticamente el bloque de código en una transacción. Confirma los cambios si el bloque se ejecuta correctamente y revierte la transacción si se produce algún error. Es más claro y menos propenso a errores que llamar manualmente a dbBegin() / dbCommit() / dbRollback().
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE ledger (account TEXT, amount REAL)')
dbExecute(con, "INSERT INTO ledger VALUES ('Alice', 1000), ('Bob', 500)")
# Automatic transaction management:
dbWithTransaction(con, {
dbExecute(con, "UPDATE ledger SET amount = amount - 100 WHERE account = 'Alice'",
params = list())
dbExecute(con, "UPDATE ledger SET amount = amount + 100 WHERE account = 'Bob'",
params = list())
})
# Committed automatically on success
print(dbGetQuery(con, 'SELECT * FROM ledger'))
dbDisconnect(con)Gestión de errores dentro de transacciones
Combinar tryCatch() con dbWithTransaction() permite informar de los errores de forma clara. Cuando el bloque interno genera un error, dbWithTransaction() revierte automáticamente la transacción y su gestor de errores puede registrar o volver a lanzar el problema.
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE inventory (item TEXT, qty INTEGER)')
dbExecute(con, "INSERT INTO inventory VALUES ('Widget', 100)")
safe_update <- function(con, item, qty_change) {
tryCatch(
dbWithTransaction(con, {
dbExecute(con,
'UPDATE inventory SET qty = qty + ? WHERE item = ?',
params = list(qty_change, item))
cat('Updated', item, 'by', qty_change, '\n')
}),
error = function(e) cat('Failed (rolled back):', e$message, '\n')
)
}
safe_update(con, 'Widget', -30)
print(dbGetQuery(con, 'SELECT * FROM inventory'))
dbDisconnect(con)Inserciones por lotes para mejorar el rendimiento
Insertar muchas filas una por una en un bucle es lento. Hay dos opciones mejores: use dbAppendTable() para insertar un marco de datos completo de una vez o incluya las inserciones individuales en una sola transacción; las bases de datos confirman las escrituras en disco una sola vez al final, por lo que una inserción masiva dentro de una transacción es mucho más rápida que las inserciones con confirmación automática.
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ':memory:')
dbExecute(con, 'CREATE TABLE events (user_id INTEGER, action TEXT)')
# Fast: insert a whole data frame at once
new_events <- data.frame(
user_id = c(101, 102, 103, 104),
action = c('login', 'view', 'purchase', 'logout')
)
dbAppendTable(con, 'events', new_events)
result <- dbGetQuery(con, 'SELECT COUNT(*) AS n FROM events')
cat('Rows inserted:', result$n)
dbDisconnect(con)dbDisconnect() — Cierre siempre las conexiones
Las conexiones de base de datos consumen recursos en el servidor. Llame siempre a dbDisconnect(con) cuando haya terminado. Usar on.exit(dbDisconnect(con)) al principio de una función garantiza que la conexión se cierre incluso si se produce un error.
library(DBI)
# Pattern: use on.exit to guarantee disconnection
run_query <- function(sql) {
con <- dbConnect(RSQLite::SQLite(), ':memory:')
on.exit(dbDisconnect(con), add = TRUE) # always runs
dbExecute(con, 'CREATE TABLE t (x INTEGER)')
dbExecute(con, 'INSERT INTO t VALUES (1), (2), (3)')
result <- dbGetQuery(con, sql)
result # con is closed automatically after return
}
df <- run_query('SELECT * FROM t WHERE x > 1')
cat('Rows:', nrow(df))
# on.exit fires after return — connection cleanly closedComprobación rápida
Necesita agrupar dos instrucciones UPDATE relacionadas para que ambas se completen correctamente o ninguna modifique la base de datos. ¿Qué enfoque es el más adecuado?
Consultas parametrizadas y transacciones — Aspectos clave
Operaciones de base de datos seguras y fiables en R con DBI:
- No concatene nunca la entrada del usuario en cadenas SQL; use siempre
params dbGetQuery(con, sql, params = list(...))— SELECT segurodbExecute(con, sql, params = list(...))— INSERT/UPDATE/DELETE seguros- Marcadores de posición:
?para SQLite/MySQL,$1/$2para PostgreSQL dbBegin()+dbCommit()+dbRollback()— control manual de transaccionesdbWithTransaction(con, { ... })— confirmación/reversión automática (opción preferida)dbAppendTable(con, 'tbl', df)— inserción masiva rápidaon.exit(dbDisconnect(con))— cierre siempre las conexiones
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ':memory:')
on.exit(dbDisconnect(con), add = TRUE)
dbExecute(con, 'CREATE TABLE transfers (from_id INT, to_id INT, amount REAL)')
# Safe parameterized insert inside a transaction:
dbWithTransaction(con, {
dbExecute(con,
'INSERT INTO transfers (from_id, to_id, amount) VALUES (?, ?, ?)',
params = list(1, 2, 250.00)
)
})
result <- dbGetQuery(con, 'SELECT * FROM transfers')
cat('Transfer recorded:', result$amount)Preguntas frecuentes
¿La lección «Consultas parametrizadas y transacciones» es gratis?
Sí — el texto completo de «Consultas parametrizadas y transacciones» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de R Academy, actualiza a CoddyKit PRO. El curso de R Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Consultas parametrizadas y transacciones»?
Pase parámetros de forma segura y gestione transacciones de base de datos de varios pasos en R. Practicas R Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar R Academy?
No se requiere experiencia previa. R Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.
¿Cuánto tiempo toma la lección «Consultas parametrizadas y transacciones»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de R Academy?
Sí. Cada lección de R Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Fundamentos de DBI y RSQLite
- Conexión a PostgreSQL y MySQL
- dbplyr: SQL mediante la sintaxis de dplyr
- Consultas parametrizadas y transacciones