Inserción eficiente (?) de datos vía RJDBC
Las bases de datos son instrumentos magníficos con dos defectos fundamentales: es difícil meter datos en ellas y es difícil sacar datos de ellas. Pero guardarlos… los guardan estupendamente.
Estos días me ha tocado subir a una base de datos tablas bastante grandes y las herramientas proporcionadas por RJDBC
para ello, esencialmente dbWriteTable
han fallado. Internet no se pone de acuerdo sobre si es un bug de RJDBC
o si la culpa la tiene el driver de la base de datos que estoy obligado a utilizar. Como fuere, me ha tocado descender un escalón de abstracción y jugar directamente con la API del driver para ejecutar prepared statements.
(Nota: parece que es usando prepared statements que se consigue el mejor rendimiento a la hora de, al menos, insertar registros en una tabla).
El código que he preparado es (se parece a) el siguiente:
library(RJDBC)
conn <- dbConnect(drv, connection.string, user, password)
# autocommit <- false
.jcall(conn@jc,"V","setAutoCommit",FALSE)
# prepared statement
ps = .jcall(conn@jc,"Ljava/sql/PreparedStatement;",
"prepareStatement",
"insert into miesquema.mi_iris values(?,?,?,?,?)")
# insert function
myinsert <- function(arg1, arg2, arg3, arg4, arg5){
.jcall(ps, "V", "setDouble", as.integer(1), arg1)
.jcall(ps, "V", "setDouble", as.integer(2), arg2)
.jcall(ps, "V", "setDouble", as.integer(3), arg3)
.jcall(ps, "V", "setDouble", as.integer(4), arg4)
.jcall(ps, "V", "setString", as.integer(5), arg5)
.jcall(ps, "V", "addBatch")
}
# data to insert
tmp <- iris
tmp$Species <- as.character(iris$Species)
# loop, sorry!
for(i in 1:nrow(iris)){
myinsert(tmp[i,1], tmp[i,2], tmp[i,3], tmp[i,4], tmp[i,5])
}
# commits
.jcall(ps,"[I","executeBatch")
dbCommit(conn)
Ahora, los comentarios.
- Efectivamente, toca hacer algo que se parece a programar en Java (es decir, utilizar
[rJava](http://cran.r-project.org/web/packages/rJava/index.html)
). - El prepared statement es
insert into miesquema.mi_iris values(?,?,?,?,?)
. Los interrogantes son los valores que hay que rellenar en el resto del código. - Dentro de
myinsert
hay líneas de la forma.jcall(ps, "V", "setDouble", as.integer(3), arg3)
. Lo que hacen es llamar a métodos de la interfazPreparedStatement
de Java. Transforman los argumentos convenientemente (véase en el enlace anterior la documentación tanto desetDouble
como de sus colegas) y los colocan, por así decirlo, sobre el interrogante correspondiente. - En el enlace anterior también puede consultarse lo que hace
.jcall(ps, "V", "addBatch")
: esencialmente, encolar un grupo de inserciones a la espera del commit. - Finalmente,
.jcall(ps,"[I","executeBatch")
ydbCommit(conn)
suben los datos definitivamente a la tabla.
En el ejemplo anterior solo he insertado 150 filas. Para inserciones más grandes, habría que entreverar las líneas
.jcall(ps,"[I","executeBatch")
dbCommit(conn)
en el bucle para forzar un commit cada, p.e., 10000 líneas. Siéntete libre de probar con otros valores.