4 de junio de 2007

Cliente-Servidor: Parametrización de Variables

Cuando manejamos la conexión a un DBMS (DataBase Management System) podemos mandar cualquier tipo de datos, no solo caracteres de texto. Aplicable a cualquier servidor de Base de Datos (Microsoft SQL Server, MySQL, PostgreSQL, Firebird, etc).

Es muy común que cuando empezamos a manejar datos de una fuente externa a VFP, es decir, a servidores de base de datos, nos encontremos con la limitación de que únicamente podemos mandar cadenas de texto en las consultas SQL.

Para apoyarnos un poco, tomaremos una típica consulta SQL...
  SELECT iID, cClave, cCliente, cRegistro
     WHERE (iID > <NumeroInferior > ) 
        AND (iID < < NumeroSuperior > )
     FROM Cliente

Donde, obviamente, el "Numero Inferior" y "Numero Superior" serían datos variables a consultar, tomados de algún formulario de VFP.

Nuestro primer intento puede ser intentar formatear la cadena, de manera a que lo que se envía sea igual que la consulta deseada:
   lcConsulta = "  SELECT iID, cClave, cCliente, cRegistro "+
                      "      WHERE (iID > " + STR(thisform.txtNumerInferior.Value) + ")"+
                      "           AND (iID < " + STR(Thisform.txtNumeroSuperior.Value) + ")"+
                      "    FROM Cliente "

Lo anterior funcionará, ya que igual, tenemos una cadena de texto y los valores, que ya han sido convertidos a cadena serán enviados tal cual a nuestro servidor.

Pero eso, al parecer, no es eficiente... Quizás lo podríamos hacer mejor, no? Pues si, como vimos en un artículo pasado, podríamos usar bloques TEXT ... ENDTEXT, para mejorar nuestra presentación:
WITH ThisForm
   lnNumeroInferior = .txtNumeroSuperior.Value
   lnNumeroSuperior = .txtNumerSuperior.Value
ENDWITH

TEXT TO lcConsulta NOSHOW TEXTMERGE
  SELECT iID, cClave, cCliente, cRegistro
     WHERE (iID > <<NumeroInferior>> ) 
        AND (iID < <<NumeroSuperior >> )
     FROM Cliente
ENDTEXT

Funciona, si, también hace lo que deseamos, enviará los datos con los valores que queremos sin ningún problema. Pero, hagamos esto un poco más complicado: ¿Qué pasará cuando querramos hacer una búsqueda en base a un dato de tipo caracter? Cambiando un poco el ejemplo, buscaremos el conjunto de datos que cumpla con la condición de encontrar los datos de un cliente con base a la clave (string) del mismo:
   SELECT iID, cCliente, cDireccion, dFechaAlta, yAdeudo
        FROM Cliente
        WHERE cClave = 'ALFKI'

Como vemos en la consulta, y en el caso específico de SQL Server, cuando se hace una query en base a datos de caracteres debemos encerrar dicho valor entre comillas sencillas, ¿sencillo? , ¿o no?
   lcCliente = Thisform.txtCliente.Value
   TEXT TO lcConsulta NOSHOW TEXTMERGE
      SELECT iID, cClave, cCliente, cDireccion
           FROM Cliente
           WHERE cClave = '<<lcCliente>>'      
   ENDTEXT

Una vez más, vemos que el ejemplo, también funciona, pero tiene un detalle: debemos formatear cada consulta de acuerdo a los requerimentos de nuestro servidor, y según sea el caso, podría que fuera diferente de un servidor a otro, además de que por lo que a mí respecta, pienso que es bastante engorroso y podría evitarse.

La Parametrización al rescate

La forma sencilla de no tener complicaciones con los distintos tipos de datos de los servidores, y los distintos tipos de datos de VFP, resulta en usar la Parametrización, ¿y que es esto?, consiste en anteponer un símbolo de interrogación justo antes de una variable:
   lcCliente = Thisform.txtCliente.Value
   TEXT TO lcConsulta NOSHOW 
      SELECT iID, cClave, cCliente, cDireccion
           FROM Cliente
           WHERE cClave = ?lcCliente
   ENDTEXT
   IF SQLExec(lnHandle, lcConsulta, lcCursor) > 0
     ***  ¿Manipular, mostrar los datos?
     *** SELECT (lcCursor)
   ELSE
      IF AERROR(laError) > 0
         Messagebox("Error en la consulta: "+laError[2])
      ELSE
         Messagebox("Error inesperado en la aplicación")
      ENDIF
   ENDIF   

Como podrás notar, en el bloque TEXT...ENDTEXT hemos prescindido de la función de TEXTMERGE, además de las comillas sencillas, y en su lugar, sólo hemos puesto un símbolo de cierre de interrogación.

¿Qué estamos haciendo?, cuando usamos la Parametrización, delegamos la función de formateo de variables al driver ODBC, VFP se encargará de comunicarle qué tipo de datos es, y casi por arte de magia, hará la conversión pertinente. ¿Sencillo?, asi es, y funcionará con todos los tipo de datos de VFP (no será el caso de objetos, no confundir). Haz el intento ;-).

Nota al margen

En los ejemplos arriba escritos hemos hecho solo consultas de datos, pero aplicará a cualquiera de los comandos de tipo DML (Data Manipulation Language) de SQL, tales como INSERT, UPDATE, DELETE, etc.:
  ** Insertar datos 
  TEXT TO lcInsert NOSHOW
    INSERT INTO Detalles (cClave, iProductoID, nCantidad, yPrecioVenta, dFechaVenta)
       VALUES (?lcClave, ?lnProductoID, ?lnCantidad, ?lyPreciodeVenta, ?ldFechaVenta)
   ENDTEXT
   IF SQLExec(lnHandle,lcInsert) > 0
      *** Datos insertados con éxito
      *** Messagebox("Datos insertados con éxito")
   ELSE
       IF AERROR(laError) > 0
          Messagebox("Error al insertar datos:"+laError[2])
       ELSE
          Messagebox("Error inesperado!!")
       ENDIF
   ENDIF

   ** Actualizar un registro 
  TEXT TO lcUpdate NOSHOW
     UPDATE Detalles SET nCantidad = ?lnCantidad 
       WHERE ( iMovimientoID = ?lnMovimiento )
  ENDTEXT
  IF SQLExec(lnHandle, lcUpdate) > 0
     *** Actualizados con exito
     *** Messagebox("Datos actualizados con éxito")
  ELSE
     IF AERROR(laError) > 0
        Messagebox("Error al actualizar el registro:"+laError[2])
     ELSE
        Messagebox("Error inesperado!!")
     ENDIF
  ENDIF

   ** Borrar registro(s) 
   TEXT TO lcDelete NOSHOW
     DELETE FROM Detalles WHERE (iFacturaID = ?lnFactura)
   ENDTEXT
   llSucess = .F.
   IF VERSION(2) >= 9
      *** Usar las nuevas características de VFP9 para conocer los registros afectados
      llSucess = SQLExec(lnHandle,lcDelete,NULL,aRowsAffected) > 0
      IF (llSucess)
         lcMessage = "Se han borrado "+STR(aRowsAffected[1,2] ) + ;
                           " artículos de la Factura "+STR(lnFactura)
      ENDIF
   ELSE
     llSucess = SQLExec(lnHandle,lcDelete) > 0
     lcMessage = "Se han borrado todos los articulos de la Factura "+str(lnFactura)
   ENDIF
   IF llSucess
      *** Registros borrados con exito
      *** Messagebox(lcMessage)
   ELSE
      IF AERROR(laError) > 0
        Messagebox("Error al intentar borrar los datos:"+laError[2])
      ELSE
        Messagebox("Error inesperado!!")
      ENDIF
   ENDIF

Espero que este artículo les sea de utilidad.

Espartaco Palma Martínez

3 comentarios :

  1. Excelente, la verdad Yo no le había tomado importancia a ? siempre mandaba lista la cadena en lo que respecta a los valores

    ResponderBorrar

Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.