La instrucción SQL Insert y su difícil legibilidad

29 comentarios

Insert

En ocasiones quedo asombrado (para mal) de que se haya aceptado como un estándar universal ciertas operaciones o instrucciones. Una de estas operaciones que me parece increíble que haya podido llegar a ser un estándar es la instrucción SQL para realizar inserciones.

Si pudiéramos calificar la maquiavélica invención de la instrucción de inserción, yo lo pondría entre el invento del paraguas (un instrumento diseñado para sacarle los ojos a los viandantes) y la invención de la batamanta (un invento diseñador para … aun no lo se).

Por alguna razón, el que diseñó esta instrucción quizá pensó que los programadores estabamos lo suficientemente aburridos y no teníamos otra cosa mejor que hacer que estar contando las posiciones para encajar los nombres de los campos que lleva la inserción y los valores correspondientes que van asociados.

Una instrucción a partir de diez campos empieza a ser incomprensible a simple vista, sobretodo si lo que se insertan son valores numéricos o valores que por su valor pueden ser cualquier cosa.

insert into tabla (campoN, campoE, campoF, campoC, campoL, campoQ, campoR, campoT, campoY, campoH, campoG campoS, campoW, campoZ, campoP) values(6,2,5,3,4,3,7,6,8,32,4,6,8,4,3)

¿Quién sabe decirme, sin contar posiciones, que campo se rellena con el valor 32?

Pero el problema no acaba aquí. En ocasiones cuando tenemos que insertar datos a veces hay que rellenar el dato y otras veces no en función de si se cumplen unas condiciones. Si generamos la consulta de manera dinámica nos topamos con el problema de la obligación de tener, no una, sino dos cadenas donde ir concatenando.

Si lo que utilizas son las consultas preparadas para solventar el problema del SQLInyection igualmente es necesario contar en que posición se encuentra el ‘?’ correspondiente e introducir el dato mediante una instrucción posterior.

Utilizar generadores de instrucciones SQL

Una de las soluciones ideales es utilizar un generador de instrucciones que nos transforme un modo de trabajo más fácil a consultas SQL. En lenguajes como Java existe JPA como estándar o puedes utilizar un ORM aunque tiene el pequeño inconveniente que hay que añadir dependencias JAR a tu proyecto. En Java también puedes utilizar ResultSet en modo update de manera que puedes modificar el ResultSet y luego ejecutar la instrucción update.

En el caso de Visual Studio también tienes ORMs como NHibernate o Entity Framework 4.x. También es posible evitar escribir los SQLs utilizando ADO.NET utilizando sqlAdapter. En PHP también dispones de ORMs como PHP Object Generator, Doctrine o ADOdb Active Record. Todas estas opciones son las ideales para el almacenamiento de datos si no quieres lidiar con el SQL directamente.

Crearse un conversor SQL

Un problema menor que tienen estas consultas es que no puedes ver la consulta final hasta que no la ha ejecutado. En ocasiones cuando se está depurando interesa ver la cadena que se va a ejecutar antes de que se ejecute. Esto viene bien para llevar mayor control de lo que se ejecuta en la base de datos mientras depuras y no tener que recurrir al log como ocurre con Hibernate.

Para ello una propuesta es crear un lenguaje más fácil de fabricar y de entender y al final ejecutarlo en un conversor a SQL.

Imagina que en la anterior consulta estuviese diseñada de esta manera:

campoN=6, campoE=2, campoF=5, campoC=3, campoL=4, campoQ=3, campoR=7, campoT=6, campoY=8, campoH=32, campoG= 4, campoS=6, campoW=8, campoZ=4, campoP=3

¿Y ahora? ¿que campo se rellena con el valor 32?

Pues bien, hacer este algoritmo es bastante sencillo si lo que deseas almacenar no tiene selects anidados. Además la cadena es igual que el utilizado para hacer updates por lo que se podría aprovechar el trabajo de uno para hacer también las modificaciones.

Aquí tenéis un pequeño algoritmo en Java que convierte la anterior cadena en una inserción. Lo que realiza es crear dos cadenas de manera que a partir que rellena la parte de los nombres hasta que encuentra un igual ‘=’ luego rellena el valor hasta encontrar una coma ‘,’ fuera de comillas simples

public class ConversorSQL {
     public String convertir(String tabla, String valores){
          String primeraParte="" , segundaParte="" ;
		
          boolean cargandoNombre=true, comillas=false;
          String separador="" , dato="", nombre="" ;

for (int i=0;i<valores.length();i++){ //Recorre la cadena caracter a caracter char caracter=valores.charAt(i); //Obtiene el caracter if (cargandoNombre){ //Si esta rellenando el nombre if (caracter=='='){ //Si encuentra el = cargandoNombre=false; //Pasa a rellenar los valores comillas=false; //Indicador de comillas para leer cadenas }else{ nombre+=caracter; //Concatena el caracter } }else{ //Si esta rellenando el dato if (caracter=='\''){ //Si encuentra una comilla comillas=!comillas; //Alterna la detección de cadenas } if ((!comillas && caracter==',')){ //Si no esta en una cadena y encuentra una coma cargandoNombre=true; //Pasa a cargar el nombre primeraParte+=separador+nombre; //Concantena el nombre segundaParte+=separador+dato; //Concantena el valor separador="," ; //El separador será la coma nombre="" ; //Inicializa el nombre dato="" ; //Inicializa el valor }else if(i==valores.length()-1){ //Si ha llegado al final de la cadena dato+=caracter; //Concatena el caracter primeraParte+=separador+nombre; //Concatena el nombre segundaParte+=separador+dato; //Concatena el valor }else{ //Si sigue leyendo caracteres del valor dato+=caracter; //Concatena el caracter } } } //Devuelve la cadena de inserción return "insert into " + tabla + "(" + primeraParte + ") " + "values (" + segundaParte + ")" ; } //Ejemplo para probar el algoritmo. public static void main(String args[]){ String ejemplo="dato1=2,dato2='hola',dato3='prueba'''" ; ConversorSQL con=new ConversorSQL(); String sql=con.convertir("mitabla", ejemplo); System.out.println(sql); } }

Quizá te preguntes que ocurre con el SQLInyection . Uno de los motivos de realizar consultas preparadas es evitar la inyección deSQL. En el texto plano esta resuelto poniendo dos comillas simples. Es decir, si se desea insertar “dato‘” en la cadena habría que poner como valor nombre=‘dato’‘’.

Finalmente con este algoritmo ya podrás hacer más fácilmente consultas con más o menos valores según ciertas condiciones.


String datos=&#34;&#34;;
si (cumpleA) entonces datos+=&#34;campo1=1,&#34;;
si (cumpleB) entonces datos+=&#34;campo2=2,&#34;;
…
si (cumpleN) entonces datos+=&#34;;campoN=99999,&#34;;
String sql=obj.convertir(datos);

Más información

Información sobre Insert | Wiki

Información sobre JPA | JSR317

Información sobre ADO.NET | Msdn Microsoft

Anunciate aquí
Anunciate aquí
Anunciate aquí

¿Quieres saber más?

Artículos

Artículos relacionados que probablemente también te interesen

Ver más

Respuestas

Preguntas sobre este tema que ha contestado la comunidad

+ Deja tu comentario

Comentarios

  • 1

    Avatar de starcarr !

    Una pequeña correción, Hibernate es Java, para .Net se usa NHibernate.

  • Respondiendo a #1:
  • 3

    !
    | 1 estrellas

    No sería mejor usar Entity Framework en .Net?

  • Respondiendo a #1:
  • 8

    Avatar de jorgerubira !

    Buenas, ya lo he modificado. Gracias.

  • 2

    !
    | 1 estrellas

    Una posible respuesta a tu pregunta sobre el valor 32, es que principalmente hay un error de sintaxis :P.

  • Respondiendo a #2:
  • 19

    Avatar de acerswap !

    ¿Son imaginaciones mias o le faltaba una coma?

  • 4

    !
    | 1 estrellas

    Con las herramientas que hay actualmente de acceso a datos fuertemente tipadas, usar SQL es, mínimo, una temeridad.

    Mi favorita, LINQ to SQL. Sencillísima, comodísima, y con un rendimiento mucho más que aceptable.

  • 5

    Avatar de starcarr !

    Por cierto, ya entrados en gastos, yo haría el método así:

    private static final String TABLE = "TABLE"; private static final String COLUMNS = "COLUMNS"; private static final String VALUES = "VALS"; private static final String STANDARD_INSERT = "INSERT INTO "+TABLE+" ("+COLUMNS+" ) VALUES ("+VALUES+" )"; private static final String GROUP_SEPARATOR = ","; private static final String VAL_DEF_SEPARATOR = "="; private static final String COLUMN_SEPARATOR = ","; private static final String VALUE_SEPARATOR = ","; private static final String SPACE=" ";

    private static String convertir(String table, String sql) {

    String[] groupArray = sql.split(GROUP_SEPARATOR); String columns = ""; String values = ""; String result = "";

    for (String par : groupArray) { par= par.trim(); columns += SPACE + par.split(VAL_DEF_SEPARATOR)[0] + COLUMN_SEPARATOR; values += SPACE + par.split(VAL_DEF_SEPARATOR)[1] + VALUE_SEPARATOR; } columns = columns.substring(0, columns.length()-1); values = values.substring(0, values.length()-1); return STANDARD_INSERT.replace(TABLE, table).replace(COLUMNS, columns).replace(VALUES, values); }

    Que si lo ejecutamos: convertir("TABLE1", "column1=1, column2=2, column3=3"), retorna "INSERT INTO TABLE1( column1, column2, column3 ) VALUES ( 1, 2, 3 )":

    Saludos.

  • Respondiendo a #5:
  • 6

    Avatar de starcarr !

    Bien, veo que desordenó el código. Pero eso es la idea xD.

  • 7

    Avatar de terminus !

    En Mysql existe la sentencia INSERT INTO tabla SET campo="algo", otrocampo="otracosa",... .Sin embargo, desconozco si es una característica de mysql o se puede usar en otras bases de datos.

  • Respondiendo a #7:
  • 21

    Avatar de Robert !
    Robert | 1 estrellas

    En principio INSERT INTO tabla SET campo="algo" es de mysql, la otra es estándar de SQL, así que serviría para todas las bbdd sql mientras que la de mysql no.

    Normalmente el SET es utilizado en las sentencias UPDATE: UPDATE tabla SET campo1 = valor1, campo2 = valor2, campo3 = valor3...

  • 9

    !

    a partir de SQL Server 2008 (para quienes lo usen) existe una sintaxis similar a la propuesta para hacer inserts:

    USE YourDB GO INSERT INTO MyTable (FirstCol, SecondCol) VALUES ('First',1), ('Second',2), ('Third',3), ('Fourth',4), ('Fifth',5)

    (Fuente)

    -- editado por última vez a las 19:22

  • Respondiendo a #9:
  • 26

    !
    | 1 estrellas

    Sin animo de criticar, me ha gustado ver el aporte y la fuente, ya que lo desconocia que en sql 2008 se podia hacer eso.

    Pero no tiene nada que ver lo que se menciona en el link con este post.

    En el link hablan de hacer varios inserts en uno solo, y no como si fuera un update.

    Aun asi, gracias, porque desconocia que se pudiera hacer eso en 2008

  • Respondiendo a #26:
  • 27

    !

    tenés razón! creo que estaba tan emocionado con compartir ese enlace que no le presté suficiente atención al artículo, jeje. gracias por el comentario!

  • 10

    Avatar de Dany Paredes !

    Que vivan los ORM :) y Linq :)

  • 11

    !
    | 1 estrellas

    me parece que es demasiado embrollo para un simple insert. si manejas bien el lenguaje no te hace falta todo eso.

  • Respondiendo a #11:
  • 13

    Avatar de Pablo Roca !

    De acuerdo contigo.

    Saber el campo que se rellena con el valor 32 sin contar, pues como que es imposible.

    Yo para esto siempre hago lineas de tres en tres campos, y así es muchísimo mas fácil saber que campo se rellena con que.

    Pero complicarme con el metodo que recomiendas .. no, mucho mas cómodo y rápido hacer un INSERT a pelo, no es nada complicado.

  • 12

    Avatar de fht233 !
    fht233 | 1 estrellas

    Menuda chapuza el montaje del insert a base de pegar strings. Justo lo que no hay que hacer jamas de los jamases! Sucio, peligroso, inmantenible, fuente inagotable de errores... y de collejas como pille a alguien poniendo eso en cualquier proyecto.

    Como minimo de los minimos, si no queda otro remedio, usa los 'prepared statements', que ya te viene con el jdbc y justo para evitar esas chapuzas lo han puesto ahi.

    Aunque la chapuza se puede mejorar con un poco de esfuerzo. Puedes poner ese chorizo de codigo en mitad de un JSP o PHP, mezclado con el HTML, passwords incluidos. Si es que me los veo venir de kms.

  • 14

    Avatar de ssanzol !

    En mi empresa está literalmente prohibido el uso de INSERT INTO... VALUES

    En su lugar, usamos INSERT INTO... SET

    Pruébalo si puedes, porque ayuda mucho a preparar lo queries de manera legible...

  • Respondiendo a #14:
  • 22

    Avatar de Robert !
    Robert | 1 estrellas

    Me parece que el INSERT INTO...SET es sólo para MYSQL.

  • 15

    !
    | 1 estrellas

    En el entorno donde me muevo que es COBOL/CISC/DB2, lo tenemos fácil con alguna que otra utilidad que nos muestra los campos. Pero como realmente insertamos registros de manera mas rápida es usando el truco del insert select, donde tenemos un registro maestro y vamos modificando los campos necesarios mientras revisamos los valores que estamos insertando.

  • 16

    Avatar de maztercom !

    Pues no me ha gustado ni lo mas minimo este trozo de codigo. Primero que todo en vez de usar String para concatenar cosas deberias usar StringBuilder que esta preparado para la manipulacion de texto (mejoras de memoria)

    Y como ya mencionaron anteriormente crear sql concatenando strings es una mala practica. Para eso esta SQLParameter.

    Ojo, que no caiga mal mi comentario, es solo mi opinion. Quizas no conocias lo que mencionaba o lo olvidaste (que suele suceder)

    Saludos.

  • Respondiendo a #16:
  • 17

    Avatar de yachar !
    yachar | 1 estrellas

    Un detalle sobre concatenar con String y con StringBuilder. He leído varias veces que, por lo menos en .NET (c#), es indiferente usar cualquiera de las dos clases EXCEPTO cuando la concatenación se realiza dentro de un bucle.

    Me suena haber leído algún blog que enseñaba el código precompilado y era el mismo en ambos casos, insisto, siempre y cuando la concatenación no esté dentro de un bucle donde no se sabe el número de concatenaciones y el tamaño final del string.

    ¿Alguien tiene algún dato más sobre esto?

  • Respondiendo a #17:
  • 20

    Avatar de maztercom !

    Si, es asi como mencionas. Aca hay un articulo donde lo explica muy bien.

    http://kaioa.com/node/59

    De hecho habla en que casos es mejor el StringBuilder y en que casos usar el String convencional.

    Es conveniento colocar la "capacidad" que el StringBuilder va a tener para ahorrar el consumo de asignacion (allocation)

    Buen punto yachar!

  • 18

    !

    Yo en PHP lo que hago es meter los datos que quiero insertar en un array, por ejemplo:

    $objInsert = array();
    $objInsert["name"] = Database::DataToStore($name);
    $objInsert["date"] = "NOW()";
    $objInsert["category"] = $category;

    Database::insertObj($objInsert, $tableName);

    y me he creado una clase Database que interpreta el array y lo inserta en la base de datos. Además de que tengo otro que es para insertar varios a la vez (Database::insertObjArray()), la verdad es que estoy muy contento con esta clase :)

    -- editado por última vez a las 10:26

  • 23

    !
    | 1 estrellas

    Definitivamente como dice Joan Perez Guerrero ¿Quien hace un INSERT concatenando a mano los valores? ¿Que no somos developers para inventar algo más eficiente?

    Facilmente en cualquier lenguaje puedes crear un arrayMap de Clave => Valor y despues unir las piezas con Implode..

    Creo que es una exageración los "grandes" inconvenientes del lenguaje SQL

  • 24

    Avatar de jraul !
    jraul | 1 estrellas

    Nombres de columnas imposibles de descifrar, campos numericos para guardar codigos postales, hay tantas cosas que me tocan los ... La solucion utilizar Hibernate.

  • 25

    Avatar de r-e-i-n-a !

    Si usas Oracle la solución mas adecuada sería usar PL/SQL para crear tus propios procedimientos y funciones para "customizar" la entrada de datos.

    Por ejemplo:

    Crear el procedimiento para insertar valores tal que:

    - Crea un trigger que se dispara antes de alterar la tabla (BEFORE INSERT), mostrando los cambios realizados.

    - Crea un savepoint antes de alterar la tabla.

    - Una vez alterada la tabla, se pregunta si se está de acuerdo con los cambios realizados.

    - Si se está de acuerdo, commit.

    - Si no se está de acuerdo, rollback to savepoint.

    - Borrar SavePoint.

    - Borrar Trigger.

    - Fin de procedimiento.

  • 28

    !
    | 1 estrellas

    Yo sencillamente haría un store procedure (cualquier motor sql que se precie lo permitirá) y pasaría parámetros. Algo que queda muy ordenado, permitiéndote separar la lógica de la aplicación de la de la base de datos, haciendo allí todo lo que tenga que ver con condiciones que la modificarán.

    Además de que todos los lenguajes de programación que se precien, te dejará definir el procedimiento con sus parámetros para poder utilizarlo en el momento que desees.

    No veo tanto problema la verdad.

  • Respondiendo a #28:
  • 29

    !
    | 1 estrellas

    Leyendo mi comentario queda demasiado serio, es solo una opinión eh. Me encanta ver debates de este tipo.

Escribir un comentario

Para hacer un comentario es necesario que te identifiques: ENTRA o conéctate con Facebook Connect

Anunciate aquí

WSL Weblogs SL