La forma más fácil de cambiar un dominio

1 comentario

Lo correcto es que cuando definimos la estructura de una tabla todas sus columnas referencien a un dominio. ¿Pero qué pasa si luego queremos cambiar el dominio de una columna?

Supongamos que en muchas tablas tenemos columnas que referencian al dominio D_NOMBRE25 (que es un VARCHAR(25)) y luego queremos usar D_NOMBRE30 (que es un VARCHAR(30)). En ese caso, ningún problema, porque pasamos de un número menor (25 en este ejemplo) a un número mayor (30 en este ejemplo).

O tenemos columnas que usan un dominio D_INTEGER que es un INTEGER y queremos pasarlas a D_BIGINT que es un BIGINT. Ningún problema tampoco, ya que pasamos de INTEGER a BIGINT, de menor a mayor, está todo ok.

Sin embargo encontraremos un problema cuando queremos hacer al revés: pasar de un número mayor a un número menor. Si queremos pasar de VARCHAR(30) a VARCHAR(25) el Firebird no lo permitirá. Tampoco permitirá pasar de BIGINT a INTEGER.

¿Por qué no permite?

Porque se puede perder precisión.

Pero yo estoy seguro que los datos cabrán bien.

No importa eso. Supongamos que quieres pasar de VARCHAR(30) a VARCHAR(25) y tú estás completamente seguro que todos los datos que tienes caben en un VARCHAR(25), que ninguno necesita más caracteres. Puedes tener razón, pero el Firebird no verifica eso. Él simplemente no permite pasar de una precisión mayor a una precisión menor. Aunque el mayor número que se guarde en una columna sea el 12, tampoco te permitirá cambiar de INTEGER a SMALLINT, por ejemplo.

¿Y cómo lo soluciono si necesito hacer esos cambios?

Tienes tres formas, dos largas y una corta.

Una forma larga es revisar todas las dependencias de cada columna, eliminar o poner comentarios en las columnas relacionadas, borrar la columna problemática y luego volver a insertarla, ya con el nuevo tipo de datos. Una vez que está insertada, recrear o quitar los comentarios de todas las dependencias.

Otra forma larga es agregar una columna adicional, con el nuevo tipo de datos, copiar todos los datos de la columna original a la nueva columna, cambiar todas las referencias a la columna original por referencias a la nueva columna y finalmente borrar la columna original.

En ambos casos, si no se trata solamente de una o dos columnas sino de decenas o de centenas de columnas, el trabajo será laborioso y demandará bastante tiempo.

Hay una forma más práctica: crear una nueva Base de Datos con los cambios deseados.

Ejemplo: Cambiar el dominio D_IDENTIFICADOR que es un BIGINT a INTEGER

DOMINIOS1

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

DOMINIOS2

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

DOMINIOS3

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

despues “Next”, “Next”, “Next”, “Finish” y “Close”.

Al finalizar el proceso, tendrás un script con el contenido de tu Base de Datos. Ahora ya es simplemente cuestión de buscar a D_IDENTIFICADOR y  cambiar su tipo de BIGINT a INTEGER.

DOMINIOS4

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

Una vez hecho eso, se ejecuta el script y listo, asunto solucionado.

DOMINIOS5

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

entonces, sin importar si el dominio D_IDENTIFICADOR había sido usado 40 veces, 70 veces, o lo que fuera, al crear una nueva Base de Datos su tipo de datos es el tipo de datos que queríamos que tuviera. Realizar todos estos pasos no tarda más de uno o dos minutos, una ganancia considerable de tiempo comparado con los otros dos métodos.

Advertencia:

Para asegurarte que todos los metadatos se hayan copiado, siempre es importante que hagas esa verificación.

DOMINIOS6

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

O sea, si la Base de Datos original tiene 44 dominios, la Base de Datos nueva también debe tener 44 dominios. Si la original tiene 80 tablas la nueva también debe tener 80 tablas, etc.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

Anuncios

Consultando sin importar mayúsculas ni acentos

1 comentario

A veces podríamos necesitar que la consulta nos traiga los datos sin importar que estén en mayúsculas, en minúsculas, acentuados o no acentuados. Por ejemplo queremos obtener: MARIA, MARÍA, maria, maría, Maria, María.

Para ello, debemos usar un ordenamiento (collation, en inglés) que termine con CI_AI ya que eso significa que al ordenar no diferencie entre mayúsculas y minúsculas ni entre vocales acentuadas y no acentuadas.

Al crear nuestra tabla, las columnas que deseamos que tengan esa característica deberían tener el atributo COLLATE ES_ES_CI_AI (si el character set de la Base de Datos es ISO8859_1) o UNICODE_CI_AI (si el character set es UTF8).

Ejemplo:

CREATE TABLE PERSONAS (
   PER_IDENTI INTEGER NOT NULL,
   PER_NOMBRE VARCHAR(40) COLLATE ES_ES_CI_AI,
   PER_APELLD VARCHAR(20) COLLATE ES_ES_CI_AI);

ALTER TABLE PERSONAS ADD CONSTRAINT PK_PERSONAS PRIMARY KEY (PER_IDENTI);

Si usamos el programa EMS SQL Manager para crear nuestras tablas entonces veríamos algo similar a esto:

INSENSITIVO3

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

Las columnas PER_NOMBRE y PER_APELLD de la tabla PERSONAS usan COLLATE ES_ES_CI_AI y por lo tanto al escribir esta consulta:

SELECT
   PER_NOMBRE,
   PER_APELLD
FROM
   PERSONAS
WHERE
   PER_NOMBRE LIKE 'MARIA%'

esto será lo que obtendremos:

INSENSITIVO1

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

o sea, todas las “María” que tenemos en nuestra tabla de PERSONAS, sin importar como se escribieron los nombres de esas personas. Claro que en el SELECT podríamos escribir UPPER(PER_NOMBRE) y UPPER(PER_APELLD) para que todos los nombres y apellidos estén en mayúsculas, y en ese caso obtendríamos:

INSENSITIVO2

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

lo cual, en general por una cuestión de estética, sería preferible. Fíjate que los nombres que se escribieron sin acentos siguen estando sin acentos, pero son mostrados, que es lo que queríamos conseguir. Si en nuestra tabla no hubiéramos usado COLLATE ES_ES_CI_AI entonces el SELECT no nos hubiera mostrado los nombres que tienen acento.

Artículo relacionado:

El índice del blog Firebird21

Forced writes

1 comentario

En cada una de nuestras bases de datos podemos determinar si los cambios que se realizan en ella se escribirán inmediatamente en el disco duro (FORCED WRITES = ON) o si se escribirán después de un tiempo (FORCED WRITES = OFF)

Si Forced Writes está en ON entonces tenemos dos ventajas:

  1. Los cambios se guardan en forma permanente. Un corte de la energía eléctrica no afectará a los datos ya guardados
  2. Los cambios se guardan siempre en el orden correcto. Por ejemplo, primero se guarda el registro y luego se guarda la entrada del índice que corresponde a ese registro

Si Forced Writes está en OFF tenemos una ventaja:

  1. Los datos se guardan mucho más rápidamente

Entonces ¿cuál es más conveniente?

En general, tener a Forced Writes en ON es lo mejor, esto es aún más notorio en Windows que en Linux. Porque cuando está en ON nos aseguramos que nunca haya pérdida de datos y que la Base de Datos siempre tenga consistencia. Si está en OFF, un corte de la energía eléctrica casi con seguridad hará que la Base de Datos se corrompa. Esa corrupción podría solucionarse, hay programas especializados en esa tarea, pero de todas maneras provocará molestias, inconvenientes y pérdida de tiempo y de dinero.

Podríamos tener Forced Writes en OFF solamente en casos especiales y muy puntuales, por ejemplo para insertar millones de filas desde una tabla externa. Una vez que esa inserción masiva ha finalizado volvemos a colocar Forced Writes en ON.

¿Cómo podemos saber si nuestra Base de Datos tiene Forced Writes en ON o en OFF?

Ejecutando el programa GSTAT.EXE que se encuentra en la carpeta \BIN de donde instalaste el Servidor del Firebird. A ese programa le pones como parámetros el nombre completo o el alias de la Base de Datos que deseas verificar y en la entrada Attributes te mostrará el estado actual de Forced Writes.

FORCED1

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

Después de escribir GSTAT y el alias de una Base de Datos se puede ver el estado de Forced Writes, que en este caso está en ON. O sea, que es lo correcto durante el uso normal.

¿Cómo se puede cambiar el estado de Forced Writes?

Mediante el programa GFIX.EXE, el cual tiene una opción llamada -write que puede ser sync (para que Forced Writes esté en ON) o async (para que Forced Writes esté en OFF)

FORCED2

 

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

Como puedes ver, al usar la opción -writes async del programa GFIX.EXE (flecha 1) el estado de Forced Writes se cambió a OFF como nos muestra el programa GSTAT.EXE (flecha 2) porque a continuación de Attributes no hay algo escrito.

Conclusión:

En general, lo conveniente es que Forced Writes esté en ON porque eso nos asegura que todos los datos se guarden y que se guarden en el orden correcto. Un corte de la energía eléctrica no ocasionará pérdidas de datos. Poner a Forced Writes en OFF puede ser muy útil cuando debemos realizar una entrada masiva de datos, pero no debemos olvidarnos de volver a ponerlo en ON apenas esa entrada masiva de datos finalice.

Artículo relacionado:

El índice del blog Firebird21

 

 

 

Usando EMS SQL Manager para copiar entre bases de datos

6 comentarios

A veces tenemos dos bases de datos que tienen tablas iguales o similares. Y como en una/s tabla/s de una de esas bases de datos ya se han insertado bastantes filas, nos gustaría simplemente copiar esas filas a la otra Base de Datos.

Hay muchas formas de realizar esa tarea, esta es una de ellas, es muy útil cuando son varias las tablas cuyos contenidos deseamos copiar aunque por supuesto podríamos usarla para copiar los datos de una sola tabla.

Veamos un ejemplo:

En una Base de Datos tenemos una tabla llamada BANCOS, que ya tiene insertadas varias filas:

EMS1

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

y esas mismas filas queremos copiar en la tabla BANCOS de otra Base de Datos. Para ello, hacemos clic con el botón derecho sobre el nombre de la Base de Datos original, o sea la que tiene las filas que deseamos insertar.

EMS2

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

en el menú contextual que nos aparece elegimos Tasks y luego Extract Database…

EMS3

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

a continuación marcamos la opción Extract all metadata and data of the database y hacemos clic en el botón “Next”

EMS4

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

ahora, elegimos la opción Automatically load to Script Editor y luego clic sobre el botón “Next”

EMS5

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

en la siguiente pantalla elegimos como queremos extraer las columnas de tipo BLOB, y en la que aparece a continuación podemos desmarcar todas las opciones porque no nos harán falta para lo que deseamos hacer

EMS6

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

después de un clic sobre el botón “Next” veremos esta pantalla:

EMS7

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

donde tendremos que hacer clic sobre el botón “Finish” para que empiece el proceso (sí, ya sé que es medio raro tener que hacer clic sobre el botón “Finish” para que algo empiece, pero bueno, así está). Cuando el proceso se haya completado hay que hacer clic sobre el botón “Close” y nos mostrará todos los metadatos y todos los datos de nuestra Base de Datos en forma de script.

Así que ahora buscamos el comando INSERT INTO BANCOS y seleccionamos y copiamos (con CTRL+C) todas las filas que  tienen ese comando:

EMS8

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

después abrimos la otra Base de Datos, y elegimos las opciones Tools y a continuación New SQL Script

EMS9

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

en la ventanita que aparece pegamos con CTRL+V lo los comandos INSERT INTO que habíamos copiado y finalmente hacemos clic en la opción Execute script

EMS10

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

y listo, asunto concluido. Todas las filas de la tabla BANCOS que se encontraban en una Base de Datos fueron copiadas a la otra Base de Datos, muy fácilmente y con muy poco esfuerzo.

Hay que aclarar que esta no es la forma más rápida de copiar las filas, pero es una forma práctica y sencilla cuando las filas a copiar no son muchas, quizás hasta unas pocas centenas y además si es algo esporádico. Si las filas son muchas o si este proceso de copia se va a realizar frecuentemente entonces hay que elegir otro método.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

Aplicaciones multimoneda

7 comentarios

Muchas veces nos gustaría que nuestra aplicación fuera multi-moneda, o sea que los importes puedan ingresarse en nuestra moneda local, en dólares, en euros, en libras esterlinas, en rublos, en yenes, etc., en la moneda que el usuario quiera.

Eso podemos conseguirlo fácilmente de la siguiente manera:

  1. Teniendo una tabla llamada MONEDAS
  2. En cada tabla donde se guardan importes guardar: cual fue la moneda utilizada y cual fue la cotización de ella. La cotización de la moneda usada por defecto (generalmente será la moneda local) siempre será 1.

MULTIMONEDA1

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

MULTIMONEDA3

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

MULTIMONEDA2

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

MULTIMONEDA4

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

Así que si ahora escribimos un SELECT como este:

SELECT
   MVC_TOTALX * MVC_COTIZA AS TOTAL_MONEDA_POR_DEFECTO
FROM
   MOVIMCAB

obtendremos todos los importes en nuestra moneda por defecto (la cual generalmente será nuestra moneda local):

MULTIMONEDA5

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

Conclusión:

Siempre es conveniente que todas nuestras aplicaciones sean multimoneda, de esa manera podremos venderlas a mayor cantidad de clientes potenciales, muchas empresas u organizaciones necesitan que sus informes sean mostrados en más de una moneda.

Artículo relacionado:

El índice del blog Firebird21

Tablas sin una PRIMARY KEY

5 comentarios

Sabemos muy bien que todas las tablas deben tener sí o sí una Primary Key (la única excepción son las tablas que siempre tendrán una fila y nunca tendrán más de una fila) pero ¿estamos seguros de que todas nuestras tablas tienen una Primary Key?

Si una tabla (que tiene o puede tener más de una fila) no tiene una Primary Key eso es un error y debemos corregirlo urgentemente. Desde luego que puedes revisar las tablas una por una, manualmente, pero eso no sería profesional.

Aquí hay un stored procedure para averiguarlo (por supuesto que no es la única forma, pero quiero mostrar como hacerlo así):

SET TERM ^ ;

CREATE PROCEDURE TABLAS_SIN_PRIMARY_KEY
   RETURNS(
      TCNOMBRETABLA VARCHAR(32))
AS
   DECLARE VARIABLE lcNombreTabla VARCHAR(32);
   DECLARE VARIABLE lcRestriccion VARCHAR(20);
   DECLARE VARIABLE lcCC VARCHAR(32);
   DECLARE VARIABLE lcMostrar VARCHAR(1);
BEGIN

   lcCC = '***';

   lcMostrar = 'F';

   FOR SELECT
      RDB$RELATION_NAME,
      RDB$CONSTRAINT_TYPE
   FROM
      RDB$RELATION_CONSTRAINTS
   ORDER BY
      RDB$RELATION_NAME
   INTO
      :lcNombreTabla,
      :lcRestriccion
   DO BEGIN
      IF (lcNombreTabla <> lcCC) THEN BEGIN
         IF (lcMostrar = 'T') THEN BEGIN
            tcNombreTabla = lcCC;
            SUSPEND;
         END
         lcCC = lcNombreTabla;
         lcMostrar = 'T';
      END
      IF (lcRestriccion = 'PRIMARY KEY') THEN
         lcMostrar = 'F';
   END

END^

SET TERM ; ^

Este stored procedure es seleccionable y por lo tanto lo debes ejecutar así:


SELECT * FROM TABLAS_SIN_PRIMARY_KEY

Artículos relacionados:

Entendiendo a las Primary Keys

El índice del blog Firebird21

Capturando excepciones del usuario

10 comentarios

Así como podemos capturar las excepciones del Firebird, las cuales tienen un número en SQLCODE o un código en GDSCODE también podemos capturar nuestras propias excepciones.

Ejemplo:

Tenemos una tabla llamada PRODUCTOS en la cual (además de otros datos) guardamos el precio de costo y el precio de venta de cada producto. Es política de la empresa que nunca el precio de venta pueda ser menor que el precio de costo aumentado en un 5%. En otras palabras, que el porcentaje de ganancia siempre debe ser 5% o más.

Esta es nuestra tabla PRODUCTOS:

CAPTURA1

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

Y esta es la tabla ERRORES, donde registramos todos los errores que vamos encontrando:

CAPTURA2

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

Esta es una excepción, que será lanzada cuando el precio de venta sea menor que el precio de costo aumentado en un 5%

CREATE EXCEPTION E_PORCENTAJE_MENOR_5 'El porcentaje de ganancia es menos que el 5%.';

Y este es un trigger de la tabla PRODUCTOS donde validamos que los precios de venta siempre sean al menos un 5% superiores al precio de costo. Y cuando no ocurre eso, insertamos una fila en la tabla ERRORES.

SET TERM ^ ;

CREATE TRIGGER PRODUCTOS_BIU FOR PRODUCTOS
       ACTIVE BEFORE INSERT OR UPDATE
       POSITION 1
AS
BEGIN

   IF (NEW.PRD_PREVTA * 100 / NEW.PRD_PRECTO - 100 < 5) THEN
      EXCEPTION E_PORCENTAJE_MENOR_5
      'El porcentaje de ganancia es menor que el 5%. Producto = ' || NEW.PRD_NOMBRE ;

   WHEN EXCEPTION E_PORCENTAJE_MENOR_5 DO
      IN AUTONOMOUS TRANSACTION DO
         INSERT INTO ERRORES
               (ERR_MODULO, ERR_COMENT)
        VALUES ('PRODUCTOS_BIU' , 'El % de ganancia del producto *' || TRIM(NEW.PRD_NOMBRE) || '* es menor que 5%') ;

END^

SET TERM ; ^

Fíjate que se sobreescribe el mensaje de la excepción y usamos un nuevo mensaje, personalizado, para indicar el nombre del producto que tuvo el problema. Desde luego que no es obligatorio sobreescribir el mensaje de la excepción, podríamos haber usado el mensaje por defecto, pero al sobreescribirlo tenemos más información. También cuando atrapamos la excepción indicamos el nombre del producto problemático para que al revisar la tabla ERRORES sea fácil saber cual fue ese producto.

Si ahora escribimos:


INSERT INTO PRODUCTOS (PRD_IDENTI, PRD_NOMBRE, PRD_PRECTO, PRD_PREVTA)
VALUES (0, 'COCA COLA 1 LITRO', 45, 60)

estará todo bien, ningún problema porque el porcentaje de ganancia es mayor que el 5% (es del 33%)

Sin embargo, si escribimos:


INSERT INTO PRODUCTOS (PRD_IDENTI, PRD_NOMBRE, PRD_PRECTO, PRD_PREVTA)
VALUES (0, 'FANTA NARANJA 1 LITRO', 45, 46)

no se insertará el producto ‘FANTA NARANJA 1 LITRO’ en la tabla PRODUCTOS. ¿Por qué no? porque ocurrió una excepción, (el porcentaje de ganancia es menor que el 5%) y cuando ocurre una excepción todo lo que se había hecho hasta ese momento se desecha. Por lo tanto el INSERT a la tabla PRODUCTOS es desechado. Pero el INSERT a la tabla ERRORES sí funciona, porque ese INSERT estaba dentro de una IN AUTONOMOUS TRANSACTION y por lo tanto se guarda sí o sí.

Si ahora revisamos el contenido de la tabla ERRORES veremos esto:

CAPTURA3

 

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

Y hemos conseguido lo que buscábamos: que cuando ocurra una excepción del usuario la capturemos.

Conclusión:

Así como podemos capturar las excepciones del Firebird (las que usan SQLCODE y GDSCODE) también podemos capturar nuestras propias excepciones. Siempre es conveniente registrar en una tabla las excepciones ocurridas para más adelante poder revisar esa tabla y descubrir que fue lo que se hizo mal.

Artículos relacionados:

Capturando excepciones

Capturando excepciones. Códigos de error predeterminados

¿Cuáles errores no se pueden atrapar?

El índice del blog Firebird21

Older Entries