Corrección a un artículo del blog

Deja un comentario

En el artículo:

Error: Too many Contexts of Relation/Procedures/Views. Maximum allowed is 255

había unos pequeños errores que ya están corregidos, así que lo aconsejable es que vuelvas a leer ese artículo, para no quedarte con información errónea en la cabeza.

Artículos relacionados:

Error: Too many Contexts of Relation/Procedures/Views. Maximum allowed is 255

El índice del blog Firebird21

El foro del blog Firebird21

Error con Global Temporary Table

Deja un comentario

Las GTT (Global Temporary Table) son muy útiles porque nos permiten tener tablas temporarias, tablas que solamente existirán mientras dure la transacción o la conexión.

Pero cuando las tablas GTT son grandes y necesitan ser ordenadas fuera del sort buffer pueden ocurrir problemas. Si usamos un disco RAM para los archivos temporales (los creados cuando en un SELECT tenemos la cláusula ORDER BY y ningún índice disponible) podríamos obtener este mensaje de error:

Unsuccessful execution caused by a system error that precludes successful execution of subsequent statements.
I/O error during “CreateFile (create)” operation for file “”.
Error while trying to create file.
The system can not find the path specified.

¿Cuál es el problema?

Que la carpeta donde se guardan los archivos temporales se encuentra en un disco RAM.

¿La solución?

Indicarle al Firebird que no use un disco RAM para los archivos temporales.

¿Qué versiones de Firebird tienen este problema?

Se lo ha detectado en Firebird 2.5.4 de 64 bits usando Windows 8.1

Podría ocurrir también en otras versiones.

Artículos relacionados:

Creando y usando tablas temporales
Usando un disco RAM para aumentar la velocidad
Los archivos temporales del Firebird
Acelerando los SORT
Configurando al Firebird
El índice del blog Firebird21
El foro del blog Firebird21

Conectando por XNET o por TCP/IP

Deja un comentario

Cuando queremos conectarnos a una Base de Datos local típicamente tenemos dos formas de hacerlo:

1. Usando XNET, por ejemplo:

C:\MiBaseDatos.FDB

2. Usando TCP/IP, por ejemplo:

localhost:C:\MiBaseDatos.FDB

Ambas tienen sus ventajas y sus desventajas. Si nos conectamos de la forma 1., o sea usando XNET la conexión será más rápida pero el problema es que si se “cuelga” la aplicación entonces el proceso FB_INET_SERVER (usado por la arquitectura Classic) permanecerá en la memoria. El problema es con Classic, ya que SuperServer y SuperClassic no tienen este problema.

Desde luego que lo correcto es que la aplicación no se “cuelgue” pero si por algún motivo eso llegara a ocurrir entonces tendremos en la memoria un proceso (o muchos procesos si se colgó muchas veces) que no debería/n estar ahí.

Entonces, si usamos la arquitectura Classic debemos tener bien presente que un “cuelgue” de la aplicación no eliminará al proceso FB_INET_SERVER de la memoria.

Este es un error que se espera sea subsanado dentro de poco tiempo, pero mientras tanto hay que recordarlo.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

Un error de concepto en la cláusula WHERE

4 comentarios

A veces, escribimos un SELECT, nos parece que está todo bien, pero sin embargo no es así. Lo miramos de arriba a abajo, de abajo a arriba y no encontramos el problema pero sin embargo … el SELECT no funciona.

Lo más probable es que ya te haya ocurrido algo así, y si ese no es el caso, no te apures … ya te ocurrirá.

Un caso de esos ví hace unos días y me pareció instructivo para mostrarlo aquí en el blog. Veamos:

Consulta 1. Un SELECT que sí funciona

SELECT
   PER_NOMBRE,
   PER_APELLD,
   CASE
      WHEN PER_ESTCIV = 1 THEN 'Soltero'
      WHEN PER_ESTCIV = 2 THEN 'Casado'
      WHEN PER_ESTCIV = 3 THEN 'Separado'
      WHEN PER_ESTCIV = 4 THEN 'Divorciado'
      WHEN PER_ESTCIV = 5 THEN 'Viudo'
   END
FROM
   PERSONAS

Este SELECT está todo ok, funcionará perfectamente, nos mostrará el nombre de una persona, su apellido, y su estado civil. El estado civil está codificado con un número que va del 1 al 5 pero lo que veremos será una palabra y eso está ok.

Consulta 2. Un SELECT que no funciona

SELECT
   PER_NOMBRE,
   PER_APELLD
FROM
   PERSONAS
WHERE
   CASE
      WHEN PER_ESTCIV = 1 THEN 'Soltero'
      WHEN PER_ESTCIV = 2 THEN 'Casado'
      WHEN PER_ESTCIV = 3 THEN 'Separado'
      WHEN PER_ESTCIV = 4 THEN 'Divorciado'
      WHEN PER_ESTCIV = 5 THEN 'Viudo'
   END

Este SELECT es muy parecido al anterior, pero sin embargo no funcionará. Lo que se hizo fue pasar el CASE en el WHERE. Eso no es un problema en sí porque podemos tener un CASE en el WHERE, sin embargo este SELECT no funciona.

¿Por qué?

Ese SELECT fue visto por gente con bastante experiencia en Firebird pero sin embargo no detectaron el problema. O tardaron en detectarlo. El autor de este blog detectó el problema al instante pero le extrañó mucho que gente experimentada con Firebird tardara tanto tiempo en encontrar el error (el SELECT problemático en realidad era otro, con otras columnas, otra tabla, muchas más columnas, pero la idea es la misma, y aquí se la muestra simplificada).

¿Dónde está el problema? ¿ya lo encontraste?

Si no es así, vuelve a mirar el SELECT y trata de encontrarlo antes de mirar la solución, porque si miras la solución no estarás usando tu cerebro.

¿Y?

¿Por qué el SELECT mostrado en la Consulta 2. no funciona?

Bien, la respuesta correcta es “porque no está completo”. Le está faltando algo. ¿Qué le está faltando?

Para saber lo que le está faltando debemos pensar en el WHERE. ¿Qué escribimos en el WHERE? Una condición para filtrar las filas que serán mostradas. Ok, todo bien hasta ahí. ¿Y cuáles son los valores posibles de una condición? que se cumple o no se cumple. En SQL eso podríamos traducir como: debe devolver verdadero o falso. Y también NULL es posible.

¿Y entonces?

Entonces, como puedes ver, al SELECT de la Consulta 2. le está faltando una comparación. El valor devuelto por el CASE debe ser comparado con algo, no puede quedarse así como está, porque está en el aire y eso está mal. Sea lo que sea que escribamos en el WHERE nos debe devolver verdadero o falso o NULL. Si devuelve cualquier otra cosa (o no devuelve algo) entonces está mal, no funcionará.

Y ese es el error de concepto. Se escribió un WHERE que no devuelve verdadero ni falso ni NULL.

Por lo tanto, un SELECT corregido y sí funcionando sería similar al siguiente:

SELECT
   PER_NOMBRE,
   PER_APELLD
FROM
   PERSONAS
WHERE
   CASE
      WHEN PER_ESTCIV = 1 THEN 'Soltero'
      WHEN PER_ESTCIV = 2 THEN 'Casado'
      WHEN PER_ESTCIV = 3 THEN 'Separado'
      WHEN PER_ESTCIV = 4 THEN 'Divorciado'
      WHEN PER_ESTCIV = 5 THEN 'Viudo'
   END
   = 'Casado'

Que nos devolverá los nombres y apellidos de las personas casadas. Ahora sí el SELECT está completo porque comparamos el valor devuelto por el CASE con algo, entonces ya está todo ok, y funcionará a la perfección.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

Cannot attach to password database

4 comentarios

El Firebird guarda los nombres de los usuarios y sus respectivas contraseñas en un archivo llamado SECURITY2.FDB (a partir de la versión 2.0, antes se llamaba SECURITY.FDB)

Si al intentar conectarte a una Base de Datos obtienes el mensaje de error: “cannot attach to password database” eso significa que el Firebird no puede acceder a ese archivo. Las causas posibles son:

  1. No existe el archivo SECURITY2.FDB en la carpeta donde instalaste el Firebird (por ejemplo, en: C:\Archivos de Programa\Firebird\Firebird_2_5\”
  2. Hay instaladas dos o más versiones de Firebird. Y por algún motivo la versión que quieres usar se confundió (este es un caso rarísimo, pero a veces ocurre)
  3. No tienes permiso para leer archivos que se encuentran en esa carpeta. Puedes verificarlo fácilmente tratando de crear en la carpeta donde instalaste el Firebird (por ejemplo, en: C:\Archivos de Programa\Firebird\Firebird_2_5) un archivo de texto llamado PRUEBA.TXT, escribe alguna palabra, graba el archivo y trata de abrirlo con el Bloc de Notas u otro programa. Si no consigues crearlo o leerlo entonces el problema es que te faltan permisos en esa carpeta.

Artículo relacionado:

El índice del blog Firebird21

 

Lock conflict on no wait transaction. Deadlock.

4 comentarios

Si al querer hacerle un UPDATE o un DELETE a una tabla recibes este mensaje de error ¿qué significa?

Que la transacción T1 hizo un UPDATE o un DELETE y antes de que la transacción T1 terminara (con un COMMIT o con un ROLLBACK) la transacción T2 también intentó hacerle un UPDATE o un DELETE a la misma tabla. Eso provocó un conflicto. Y como el modo de bloqueo de la transacción T2 es NO WAIT entonces cuando ocurre un conflicto inmediatamente recibe el mensaje de error correspondiente.

LOCK CONFLICT

Captura 1. Si haces clic en la imagen la verás más grande

¿Y cuál es la solución?

Eso depende de las circunstancias.

  • Si la operación que quiso hacer la transacción T2 (es decir, el UPDATE o el DELETE) puede esperar entonces se termina la transacción T2 (con un ROLLBACK) y más tarde se inicia una transacción T3 para que realice la operación fallida.
  • Si la operación que quiso hacer la transacción T2 no puede esperar (quizás porque la transacción T1 jamás podría finalizar normalmente con un COMMIT o con un ROLLBACK debido a que dicha transacción quedó “colgada” porque un usuario presionó las teclas CTRL+ALT+DEL o algo similar) entonces hay que eliminar a la transacción problemática. Este es el caso más complicado.

Eliminando la transacción que causa el bloqueo

En la Captura 1. podemos ver que hay un conflicto y la causa de ese conflicto es que la transacción 133608 está bloqueando los UPDATE y los DELETE a las filas de una tabla. El programa que había iniciado a la transacción 133608 fue cerrado abruptamente por el “Administrador de tareas” del Windows y es por lo tanto imposible que alguna vez la transacción 133608 finalice con un COMMIT o con un ROLLBACK, como debería ser.

¿Y entonces?

Entonces debemos eliminar a la transacción problemática (la número 133608, en este ejemplo) de forma manual. Eso lo conseguiremos con el siguiente comando:

DELETE FROM
   MON$TRANSACTIONS
WHERE
   MON$TRANSACTION_ID = 133608;

COMMIT;

La tabla MON$TRANSACTIONS es una tabla de monitoreo, interna del Firebird, o sea que siempre existe en todas las Bases de Datos. En esa tabla se guardan los datos de cada transacción. Si se elimina una fila de esa tabla entonces la transacción correspondiente deja de existir.

Por supuesto, no te olvides de terminar tu transacción eliminadora con un COMMIT.

¿Pero y si falla el DELETE?

Hay ocasiones en las que el DELETE a una fila de la tabla MON$TRANSACTIONS te fallará. ¿Qué puedes hacer entonces? Finalizar a la transacción T2 con un ROLLBACK. Si eso no es suficiente, salir de todos los programas que usan a esa Base de Datos. Y si sigue sin ser suficiente entonces detener el Servidor del Firebird y luego reiniciarlo (algo que no siempre es posible porque los demás usuarios se quejarán hasta en chino).

Pero … cuidado, porque esto no termina aquí. La transacción problemática (la 133608 en este ejemplo) dejó basura dentro de la Base de Datos y esa basura deberás eliminarla alguna vez (haciendo un sweep). No hagas el sweep cuando mucha gente está usando la Base de Datos porque el sweep siempre causa que todas las operaciones se vuelvan muy lentas.

Artículos relacionados:

Bloqueos mortales

Modos de bloqueo de las transacciones

Entendiendo sweep y garbage collection

El índice del blog Firebird21

 

Capturando una excepción y continuando con el procesamiento

7 comentarios

Supongamos la siguiente situación: tenemos que insertar 100 filas en una tabla, pero no serán insertadas manualmente sino leídas desde otra tabla o archivo. En otras palabras, un proceso por lotes.

Pero si alguna de esas 100 filas tiene algún problema el proceso debería continuar. La fila problemática no será insertada (o será insertada con alguno de sus valores originales cambiados) pero las restantes 99 filas sí deberán ser insertadas.

Por ejemplo, la fila 27 tiene un error: no cumple con una restricción UNIQUE KEY.

Así que aquí tenemos dos opciones:

  1. No capturamos la excepción que detecta la violación a las restricciones UNIQUE KEY
  2. Capturamos la excepción que detecta la violación a las restricciones UNIQUE KEY

Si elegimos la opción 1. entonces el Servidor del Firebird cuando encuentre una violación a la restricción UNIQUE KEY detendrá el procesamiento y las filas 27 a 100 nunca serán insertadas. Las filas 1 a 26 serán insertadas o no dependiendo de como se manejan las transacciones: si después de cada INSERT hay un COMMIT entonces sí habrán sido insertadas, pero si el COMMIT se ejecutará solamente después de procesar las 100 filas, entonces no serán insertadas. Todo esto implica que si elegimos la opción 1. entonces es seguro que (algunas, muchas, o todas) las filas no serán insertadas si ocurre una violación a la restricción UNIQUE KEY.

Si elegimos la opción 2. tenemos más control sobre lo que ocurrirá cuando se detecte una violación a la restricción UNIQUE KEY. Al capturar una excepción:

  • Podemos manejarla y “engañarle” al Firebird diciéndole que tal excepción nunca ocurrió
  • Podemos manejarla y decirle al Firebird que continúe con sus acciones normales. O sea que a partir de ahí continuará como si hubiéramos elegido la opción 1. Decirle al Firebird que continúe con sus acciones normales después de que nosotros hayamos manejado la excepción se llama “relanzar la excepción”.

En general, aunque no es obligatorio, es muy útil escribir en una tabla de LOG los errores que causaron que una excepción ocurriera. Así, más adelante podremos revisar esa tabla de LOG y descubrir que fue lo que anduvo mal. Y tomar las medidas adecuadas para que no vuelva a ocurrir.

Múltiples niveles

A veces, tenemos un caso como el siguiente:

El stored procedure 1 llama al stored procedure 2 el cual llama al stored procedure 3

Servidor de Firebird —> stored procedure 1 —> stored procedure 2 —> stored procedure 3

¿Qué ocurre con las excepciones en ese caso?

Si la excepción ocurrió en el stored procedure 3 entonces éste puede manejarla o no. Si la manejó puede relanzarla o no. Si no la manejó o la relanzó entonces la excepción llegará al stored procedure 2. Así mismo, el stored procedure 2 puede manejar la excepción que recibió del stored procedure 3 o no. Si no la manejó o si la relanzó entonces la excepción llegará al stored procedure 1. También el stored procedure 1 puede manejar o no la excepción que le llegó desde el stored procedure 2. Si no la manejó o si la relanzó entonces la excepción llegará al Servidor del Firebird.

Si la excepción llega al Servidor del Firebird entonces allí mismo se detiene el procesamiento. Se termina.

Pero si la excepción nunca llegó al Servidor del Firebird entonces el procesamiento continuará normalmente, como si la excepción nunca hubiera ocurrido.

En nuestro ejemplo de las 100 filas a insertar con la fila 27 teniendo problemas, si alguno de los stored procedures capturó la excepción y no la relanzó entonces el Servidor del Firebird nunca sabrá que tal excepción ocurrió. Y por ello continuará procesando normalmente a las restantes filas.

CREATE PROCEDURE MISTOREDPROCEDURE3
AS
   DECLARE VARIABLE lcNombre VARCHAR(128);
BEGIN

   FOR SELECT
      BAN_NOMBRE
   FROM
      BANCOS
   INTO
      :lcNombre
   DO BEGIN
      INSERT INTO BANCOS_NUEVOS
                 (BAN_NOMBRE)
          VALUES (:lcNombre);
      WHEN SQLCODE -803 DO BEGIN -- Violación de una UNIQUE KEY
         IN AUTONOMOUS TRANSACTION DO BEGIN
            INSERT INTO LOG
                       (LOG_COMENT)
                VALUES ('ERROR AL INSERTAR EL BANCO: ' || :lcNombre);
         END
      END
   END

END;

Tenemos dos tablas: BANCOS y BANCOS_NUEVOS, y queremos que los nombres de todos los Bancos que se encuentran en la tabla BANCOS se inserten en la tabla BANCOS_NUEVOS. Pero como la tabla BANCOS_NUEVOS ya tiene algunas filas entonces algunos nombres podrían estar repetidos, violando por lo tanto una restricción UNIQUE KEY. Pero todos los nombres que no estén repetidos deben ser insertados en la tabla BANCOS_NUEVOS.

El stored procedure MiStoredProcedure3 se encarga de esa tarea. Como la excepción -803 (que detecta las violaciones a la restricción UNIQUE KEY) no fue relanzada entonces ni el stored procedure MiStoredProcedure2 ni el stored procedure MiStoredProcedure1 ni el Servidor del Firebird se enterarán de que tal excepción ocurrió alguna vez.

¿Queremos que el stored procedure MiStoredProcedure2 se entere de que ocurrió la excepción -803? Entonces debemos escribir la palabra EXCEPTION para relanzarla.

WHEN SQLCODE -803 DO BEGIN -- Violación de una UNIQUE KEY
   IN AUTONOMOUS TRANSACTION DO BEGIN
      INSERT INTO LOG
                 (LOG_COMENT)
          VALUES ('ERROR AL INSERTAR EL BANCO: ' || :lcNombre);
   END
   EXCEPTION;
END

Aquí, se maneja la excepción -803 (la cual indica que ocurrió una violación a la restricción UNIQUE KEY) escribiendo en un archivo de LOG el error ocurrido. Luego, se relanza la excepción para que el stored procedure MiStoredProcedure2 se entere de que ocurrió esa excepción. E igualmente puede manejarla o no.

Resumiendo:

Cuando ocurre una excepción dentro de un stored procedure o de un trigger tenemos tres posibilidades:

  1. No manejarla
  2. Manejarla y no relanzarla
  3. Manejarla y relanzarla

Si no la manejamos entonces subirá un nivel. Si en ese nivel tampoco la manejamos subirá otro nivel. Y así hasta llegar al Servidor del Firebird el cual detendrá el procesamiento.

Si la manejamos y no la relanzamos entonces el nivel superior nunca se enterará de que la excepción ocurrió.

Si la manejamos y la relanzamos entonces el nivel superior sabrá que ocurrió esa excepción en el nivel inferior.

Conclusión:

Las excepciones son una herramienta buenísima de la que disponemos y que debemos utilizar para detectar y manejar cualquier error que ocurra dentro de un stored procedure o de un trigger.

Si nosotros no manejamos a las excepciones entonces el Servidor del Firebird se encargará de ello, pero eso implica que el proceso que estábamos realizando se detendrá.

Si no queremos que el proceso se detenga entonces debemos capturar la excepción y no relanzarla, de esa manera el Servidor del Firebird no se enterará de que la excepción ocurrió y no detendrá el proceso que estábamos realizando.

Artículos relacionados:

Entendiendo las excepciones

Capturando excepciones

Capturando excepciones. Códigos de error predeterminados

Capturando excepciones del usuario

El índice del blog Firebird21

El foro del blog Firebird21

 

Older Entries