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 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.
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…