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 myinserthay líneas de la forma.jcall(ps, "V", "setDouble", as.integer(3), arg3). Lo que hacen es llamar a métodos de la interfazPreparedStatementde Java. Transforman los argumentos convenientemente (véase en el enlace anterior la documentación tanto desetDoublecomo 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.
 
	