¿Es conveniente usar stored procedures?

1 comentario

Como seguramente ya sabes, en Firebird puedes escribir tu propio código en bloques llamados “stored procedures“. No es obligatorio hacerlo porque esos mismos comandos podrías escribir en tu lenguaje de programación (Visual FoxPro, Visual Basic, Delphi, C, C++, Java, etc.) directamente o por medio de EXECUTE BLOCK.

Entonces: ¿es conveniente usar stored procedures?

La respuesta es un rotundo sí y los motivos son los siguientes:

  1. El stored procedure puede ser llamado desde cualquier lenguaje de programación (Visual FoxPro, Visual Basic, Delphi, C, C++, Java, etc.). Esto implica que un programador puede trabajar con un lenguaje y otro programador con otro lenguaje y ambos obtendrán los mismos resultados
  2. Si trabajas en equipo los stored procedures estarán disponibles para todos quienes tengan permiso sobre ellos
  3. Como el Firebird puede ser usado en varios sistemas operativos, la aplicación de un programador podría estar en Windows y la de otro programador en Linux y ambos podrían usar el mismo stored procedure de la misma Base de Datos
  4. Como el stored procedure está compilado dentro de la Base de Datos su ejecución es mucho más rápida que los comandos equivalentes escritos en un lenguaje de programación
  5. Si escribes algo erróneo el Firebird lo detectará y te lo hará saber al instante. Por ejemplo si escribiste el nombre de una tabla o de una columna inexistentes o si la sintaxis de algún comando es incorrecta no podrás grabar el stored procedure hasta corregir esos errores.
  6. El tiempo de desarrollo es mucho menor ya que si usas un programa de administración gráfica tal como EMS SQL Manager o Flame Robin al instante de escribir el stored procedure ya lo puedes ejecutar; en cambio si escribes los comandos del Firebird dentro del código fuente de tu programa tendrás que compilarlo, ejecutarlo, posiblemente hacer varios clic hasta llegar al lugar adecuado para poder verificar que funcione. Como normalmente tu código fuente no funcionará al 100% la primera vez, tendrás que repetir ese proceso varias veces con la consiguiente pérdida de tiempo.

El punto 5. le otorga confiabilidad a tu aplicación, porque de lo contrario podrías tener a un usuario descontento porque tu programa se le colgó justo cuando tiene que terminar esa tarea tan importante.

En síntesis, siempre que debas manipular datos deberías hacerlo sí o sí dentro de un stored procedure. En tu lenguaje de programación solamente deberías ejecutar stored procedures y vistas, nada más. Eso, si quieres tener seguridad y confiabilidad. Y la tranquilidad de saber que todo funciona y funcionará bien.

Conclusión:

Los stored procedures son una herramienta muy poderosa que nos provee el Firebird y deberíamos usarlos al 100% para que nuestras aplicaciones sean robustas y altamente confiables. Los únicos comandos del Firebird que deberíamos escribir en nuestras aplicaciones son SELECT * FROM MiVista y EXECUTE PROCEDURE MiStoredProcedure.

Artículos relacionados:

Entendiendo a los stored procedures

El índice del blog Firebird21

El foro del blog Firebird21

Anuncios

Maestro/detalle en Visual FoxPro y Firebird

9 comentarios

Es normal que en nuestras aplicaciones tengamos programas donde los usuarios introducen datos en tablas maestro/detalle.

El proceso es el siguiente:

Inicio de la transacción
   Grabación de la cabecera, retornando la Primary Key de esa cabecera
   Si la grabación de la cabecera estuvo ok
      Grabación de los detalles, guardando en una columna la Primary Key de la cabecera
   Fin si
   Si todo estuvo ok
      COMMIT
   Fin si
   Si hubo algún problema (con la grabación o con el COMMIT)
      ROLLBACK
   Fin si
Fin de la transacción

No siempre un COMMIT finaliza exitosamente, hay varios motivos por los cuales puede fallar (por ejemplo: problemas con la red o una transacción diferente tiene bloqueada una fila) y por lo tanto debemos verificar si tuvo éxito o no.

Primero, creamos un stored procedure para grabar la cabecera:

CREATE PROCEDURE GRABAR_COMPRASCAB(
   Identi TYPE OF COLUMN COMPRASCAB.CMC_IDENTI,
   NroDoc TYPE OF COLUMN COMPRASCAB.CMC_NRODOC)
RETURNS(
   tnIdenti TYPE OF COLUMN COMPRASCAB.CMC_IDENTI)
AS
BEGIN

   UPDATE OR INSERT INTO COMPRASCAB
                   (CMC_IDENTI, CMC_NRODOC)
             VALUES(:Identi   , :NroDoc)
   RETURNING
      CMC_IDENTI
   INTO
      tnIdenti;

END

Para simplificar y no hacerlo muy largo este stored procedure tiene solamente dos columnas, aunque lo normal es que tenga muchas más. Fíjate que retorna el valor de la columna CMC_IDENTI en un parámetro de retorno llamado tnIdenti. Eso significa que desde otros programas o stored procedures podremos consultar el valor de tnIdenti. Como se usa el comando UPDATE OR INSERT eso requiere que alguna de las columnas sea la Primary Key. En este ejemplo la Primary Key es la columna CMC_IDENTI, cuyo valor se retorna en el parámetro tnIdenti.

Luego, creamos el stored procedure para grabar los detalles:

CREATE PROCEDURE GRABAR_COMPRASDET(
   Identi TYPE OF COLUMN COMPRASDET.COM_IDENTI,
   IdeCab TYPE OF COLUMN COMPRASDET.COM_IDECAB,
   IdePrd TYPE OF COLUMN COMPRASDET.COM_IDEPRD,
   Cantid TYPE OF COLUMN COMPRASDET.COM_CANTID,
   Precio TYPE OF COLUMN COMPRASDET.COM_PRECIO)
AS
BEGIN

   UPDATE OR INSERT INTO COMPRASDET
                   (COM_IDENTI, COM_IDECAB, COM_IDEPRD, COM_CANTID, COM_PRECIO)
            VALUES (:Identi   , :IdeCab   , :IdePrd   , :Cantid   , : Precio   );

END

Fíjate que en la segunda columna (COM_IDECAB) se guarda el identificador del maestro (o cabecera).

Todos los identificadores son asignados en triggers before insert, similares al siguiente:

CREATE TRIGGER BI_COMPRASCAB_CMC_IDENTI FOR COMPRASCAB
   ACTIVE BEFORE INSERT
   POSITION 0
AS
BEGIN

   IF (NEW.CMC_IDENTI IS NULL OR NEW.CMC_IDENTI = 0) THEN
      NEW.CMC_IDENTI = GEN_ID(COMPRASCAB_CMC_IDENTI_GEN, 1);

END

O sea que si el nuevo identificador es NULL o es cero entonces se aumenta el valor del generador en 1 y ese valor se asigna al nuevo identificador.

En nuestro programa Visual FoxPro escribiríamos:

M.CMC_IDENTI = 0     && Ponemos cero cuando queremos grabar una nueva compra para que el trigger le asigne el valor
M.CMC_NRODOC = ThisForm.Text1.Value     && Le asignamos el valor al número del documento

lcComando = "EXECUTE PROCEDURE GRABAR_COMPRASCAB(?M.CMC_IDENTI, ?M.CMC_NRODOC)"

lnResultado = SQLExec(gnHandle, lcComando)

IF lnResultado > 0         && Si se ejecutó el stored procedure GRABAR_COMPRASCAB exitosamente
   lnIdeCab = tnIdenti     && Le asignamos a la variable lnIdeCab el valor retornado por el stored procedure GRABAR_COMPRASCAB
   select TEMP             && En la tabla temporal TEMP tenemos los valores que queremos grabar en COMPRASDET
   Locate                  && Se ubica en el primer registro de TEMP. Es lo mismo que escribir GO TOP pero más rápido
   SCAN WHILE lnResultado > 0     && Recorrerá los registros de TEMP mientras lnResultado sea mayor que cero y no se llegue a EOF()
      M.COM_IDENTI = 0              && Ponemos cero para que el trigger le asigne su valor
      M.COM_IDECAB = lnIdeCab       && En lnIdeCab tenemos el valor del identificador del maestro (cabecera)
      M.COM_IDEPRD = TEM_IDEPRD     && En TEM_IDEPRD tenemos el identificador del producto
      M.COM_CANTID = TEM_CANTID     && En TEM_CANTID tenemos la cantidad comprada
      M.COM_PRECIO = TEM_PRECIO     && En TEM_PRECIO tenemos el precio de compra unitario
      lcComando = "EXECUTE PROCEDURE GRABAR_COMPRASDET(?M.COM_IDENTI, ?M.COM_IDECAB, ?M.COM_IDEPRD, ?M.COM_CANTID, ?M.COM_PRECIO)"
      lnResultado = SQLExec(gnHandle, lcComando)
   ENDSCAN
ENDIF

IF lnResultado > 0     && Si todo fue grabado exitosamente
   lnResultado = SQLExec(gnHandle, "COMMIT")     && Se ejecuta el COMMIT
ENDIF

IF lnResultado < 0     && Si ocurrió un error al grabar o con el COMMIT
   =AError(laError)     && Guardamos en el vector (array) laError los detalles del error ocurrido
   =SQLExec(gnHandle, "ROLLBACK")
   =MessageBox("Ocurrió algún error. La compra no fue grabada. " + laError[2])
ENDIF

En la variable lnResultado tenemos el resultado de la ejecución de cada comando SQL. Si su valor es mayor que cero entonces se ejecutó exitosamente, si es menor que cero entonces ocurrió algún error. En este caso usamos la función AERROR() para obtener los datos del error ocurrido; en el segundo elemento del vector que recibió como parámetro tenemos la descripción del error, la cual mostramos al usuario.

Si la variable lnResultado tiene el valor cero significa que el comando aún está ejecutándose. Se lo utiliza en procesamientos asincrónicos, como veremos en otro artículo.

Artículos relacionados:

Maestro/Detalle. Como averiguar el identificador del Maestro

El índice del blog Firebird21

El foro del blog Firebird21

Tamaño de la Base de Datos crece después de restaurarla

1 comentario

Si haces un backup y luego un restore de ese backup puedes encontrarte con la sorpresa de que el tamaño de tu Base de Datos ha crecido mucho. ¿Por qué?

El comando GBAK tiene dos parámetros:

– FIX_FSS_D     (usado para reparar datos UNICODE_FSS durante el restore)

– FIX_FSS_M     (usado para reparar metadatos UNICODE_FSS durante el restore)

los cuales debes utilizar una sola vez al restaurar una Base de Datos desde una versión anterior del Firebird a la versión 2.x. Si los utilizas más de una vez todo estará normal, pero el tamaño de tu Base de Datos aumentará mucho, sin necesidad, y por lo tanto desperdiciarás mucho espacio en el disco duro.

Para hacer el backup

GBAK -t -v -g -user SYSDBA -password masterkey D:\Databases\MIBASE.FDB D:\Backups\MIBASE.FDK

Para restaurar el backup (la primera vez, desde una versión anterior a la versión 2.x)

GBAK -c -v -t -rep -p 8192 -user SYSDBA -password masterkey D:\Backups\MIBASE.FDK D:\Backups\MIBASE.FDB -FIX_FSS_D ISO8859_1 -FIX_FSS_M ISO8859_1

Para restaurar el backup (todas las demás veces)

GBAK -c -v -t -rep -p 8192 -user SYSDBA -password masterkey D:\Backups\MIBASE.FDK D:\Backups\MIBASE.FDB

Artículo relacionado:

El índice del blog Firebird21

 

Manejo de variables en VFP y en Firebird

3 comentarios

Aunque hay mucha similitud, no existe una conversión directa entre todos los tipos de datos de Firebird al Visual FoxPro. Las diferencias son las siguientes:

Tipo numérico

La única diferencia es que el tipo BIGINT de Firebird pasa como CHAR(19) a Visual FoxPro. Para convertir una columna de tipo BIGINT a su valor numérico hay que usar la función VAL(). Ejemplo:

ThisForm.Text1.Value = Val(CLI_IDENTI)     && la columna CLI_IDENTI es de tipo BIGINT

Tipo alfanumérico

No hay diferencias. Si se necesita se puede usar la función CAST() para acortar la longitud. Ejemplo:

SELECT CAST(MiComentario AS VARCHAR(128)) AS MiColumnaChar FROM MiTabla

NOTA: Si el argumento de la función VARCHAR() es menor que el ancho de la columna que se está “casteando” ocurrirá un error. En el ejemplo anterior, si la columna MiComentario tiene un ancho de 140 entonces ocurrirá un error “string right truncation” porque se la acortó a 128. Sin embargo, igualmente se mostrará la columna.

Tipo fecha

En SQL no se permiten fechas vacías, en Visual FoxPro sí. En SQL todas las fechas deben tener un valor o ser NULL. Para lidiar con fechas NULL en Visual FoxPro escribiríamos:

ThisForm.Text2.Value = IIF(!ISNULL(MiFechaFirebird), MiFechaFirebird, {})

Adicionalmente, podríamos asignarle un valor predeterminado cuando la fecha sea NULL, por ejemplo:

ThisForm.Text2.Value = IIF(!ISNULL(MiFechaFirebird), MiFechaFirebird, DATE(2013, 6, 20))

le asignaría la fecha 20 de junio de 2013 a todas las fechas NULL

Grabación de fechas

Pueden ocurrir problemas cuando se quiere guardar una fecha de VFP en Firebird, para evitarlos se agrega un caracter CHR(0) al final. Como la grabación de fechas es algo muy común lo más conveniente es tener una función que realice la tarea de convertir la fecha de VFP a fecha de Firebird:

FUNCTION FECHA_FIREBIRD_1 && Le agrega un caracter CHR(0) al final. Se usa cuando se AGREGAN o MODIFICAN datos
LParameters tuFecha
Local lcFechax

   lcFechax = iif(VarType(tuFecha) == "D", DtoC(tuFecha), tuFecha)

   lcFechax = SubStr(lcFechax, 4, 2) + "-" + Left(lcFechax, 2) + "-" + Right(lcFechax, 4) + Chr(0)

Return(lcFechax)

Esa función la usaríamos así:

M.VEN_FECHAX = Fecha_Firebird_1(ThisForm.Text3.Value)

lcComando = “EXECUTE PROCEDURE GRABAR_VENTA(?M.VEN_FECHAX)”

lnResultado = SQLExec(lnHandle, lcComando)

Además, cuando lo que queremos es consultar datos es conveniente también tener una función que convierta las fechas:

FUNCTION FECHA_FIREBIRD_2 && Sin el caracter CHR(0) al final. Se usa cuando se CONSULTAN datos
LParameters tuFecha
Local lcFechax

   lcFechax = iif(VarType(tuFecha) == "D", DtoC(tuFecha), tuFecha)

   lcFechax = SubStr(lcFechax, 4, 2) + "-" + Left(lcFechax, 2) + "-" + Right(lcFechax, 4)

Return(lcFechax)

A esta función la usaríamos así:

lcFechaVenta = Fecha_Firebird_2(ThisForm.Text2.Value)

lcComando = “SELECT VEN_FECHAX FROM VENTAS WHERE VEN_FECHAX = ?lcFechaVenta”

lnResultado = SQLExec(lnHandle, lcComando)

Tipo boolean

Firebird no dispone de un tipo boolean (se tiene previsto que en la versión 3 si haya) por lo cual generalmente se usa un dominio para emularlo. Algo como:

CREATE DOMAIN D_BOOLEAN AS
   CHAR(1)
   CHECK (VALUE = 'F' OR VALUE = 'T');

Entonces en nuestro programa VFP podríamos escribir:

llMiValorBoolean = MiColumnaFirebird == “T”

y así, la variable llMiValorBoolean tendrá .T. cuando MiColumnaFirebird sea “T” y tendrá .F. cuando tenga “F”.

si el código de arriba no te resulta claro también podrías escribir:

llMiValorBoolean = IIF(MiColumnaFirebird == “T”, .T., .F.)

Artículo relacionado:

El índice del blog Firebird21