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

Anuncios