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).
  • 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 interfaz PreparedStatement de Java. Transforman los argumentos convenientemente (véase en el enlace anterior la documentación tanto de setDouble 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") y dbCommit(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.

Un comentario sobre “Inserción eficiente (?) de datos vía RJDBC

  1. ffernandez 27 mayo, 2014 13:34

    Estoy teniendo un problema parecido estos días, que he «solucionado» provisionalmente volcando los datos a un txt y ejecutando la correspondiente sentencia «LOAD». A ver si saco un rato para probar esto que parece algo más elegante…

Los comentarios están desabilitados.