Entendiendo a GSTAT (4)

Deja un comentario

Entender como funciona el programa GSTAT es bastante largo. Ya hemos visto algo sobre él en estos artículos:

Entendiendo a GSTAT (1)

Entendiendo a GSTAT (2)

Entendiendo a GSTAT (3)

ahora, continuamos.

Opción -index

Esta es la opción que debemos elegir cuando solamente nos interesan los índices de nuestras tablas.

gstat01

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

Como es lo usual, la contraseña la extraemos de un archivo de texto y la salida del programa GSTAT la enviamos a otro archivo de texto. Esto último es para facilitarnos la tarea.

gstat02

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

En la Captura 2. vemos una parte del contenido del archivo de texto que nos muestra información sobre nuestros índices. Los nombres de los índices siempre aparecen ordenados alfabéticamente.

¿Cuál es el significado de lo que estamos viendo?

ASIENTOSCAB es el nombre de la tabla

131 es el identificador que la tabla ASIENTOSCAB tiene dentro de la tabla del sistema RDB$RELATIONS. Es en RDB$RELATIONS donde se  guardan los nombres de todas las tablas de la Base de Datos.

ASC01, ASC02, ASC03, y PK_ASIENTOSCAB son los nombres de los índices

0, 1, 2, 3, los números entre paréntesis que vemos después de los nombres de los índices son los identificadores de los índices menos 1 y nos indican el orden en el cual fueron creados, siendo 0 el primer índice que creamos, 1 el segundo índice que creamos, 2 el tercer índice que creamos y así sucesivamente. Si se creó un índice y luego se lo eliminó, el número que tenía no aparecerá en la lista.

Listado 1.

SELECT
   RDB$INDEX_ID,
   RDB$RELATION_NAME,
   RDB$INDEX_NAME
FROM
   RDB$INDICES
WHERE
   RDB$RELATION_NAME = 'ASIENTOSCAB'
ORDER BY
   RDB$INDEX_ID

gstat03

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

¿Por qué en la Captura 2. no muestra los verdaderos identificadores sino los identificadores menos 1? Es un verdadero misterio, quizás haya alguna razón valedera para ello pero es dudoso que exista. Probablemente sólo sea por costumbre.

Depth es la cantidad de indirecciones o desviaciones que hay en el árbol B-tree del índice. Lo ideal es que ese número sea como máximo 3. Si es mayor que 3 entonces el índice no será tan eficiente como podría ser ¿cómo lo solucionamos? aumentando el tamaño de la página de nuestra Base de Datos. Si por ejemplo el tamaño de la página es 4096 lo aumentamos a 8192 y volvemos a ejecutar a GSTAT con la opción -index para comprobar si Depth ahora es 1, 2, ó 3 (esos son los mejores valores que puede tener Depth). Si sigue siendo mayor que 3 entonces volvemos a aumentar el tamaño de la página de nuestra Base de Datos y ahora lo ponemos en 16384.

Leaf buckets es la cantidad de páginas que están en el nivel más bajo del árbol B-tree. Es en estas páginas donde se guardan los punteros a las filas de la tabla. En las demás páginas de índice se guardan los enlaces a otras páginas de índice.

Nodes es la cantidad total de filas en la tabla que han sido indexadas. Sin embargo, este número puede ser erróneo porque podrían aparecer filas que han sido borradas con DELETE y aún su basura no ha sido recolectada o también porque las columnas del índice cambiaron de valor. Por eso, es conveniente ejecutar a GSTAT con la opción -index solamente después de un sweep o de un ciclo backup/restore.

Average data length es el tamaño en bytes promedio de los datos de la columna (o columnas) que se indexaron. Como Firebird comprime esos datos antes de grabarlos en una página de índices, el average data length será siempre menor a la suma del tamaño de las columnas de la tabla.

Total dup es la cantidad total de duplicados que tiene un índice. Los índices que se utilizan en las restricciones Primary Key y Unique Key no admiten duplicados, pero los otros índices sí los admiten. Cuantos más duplicados haya, peor es el índice.

 Listado 2.

SELECT
   ASC_ANOEJE,
   ASC_CODSUC,
   ASC_NUMERO,
   COUNT(*)
FROM
   ASIENTOSCAB
GROUP BY
   ASC_ANOEJE,
   ASC_CODSUC,
   ASC_NUMERO

gstat05

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

gstat04

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

En la Captura 4. vemos que al ejecutar el Listado 2. se usó el índice ASC01, y en la Captura 5. vemos la cantidad de veces que los valores de ese índice aparecieron. En este caso no hay duplicados, ya que COUNT siempre nos muestra el número 1 pero en otros índices sí podría haber duplicados. El Listado 2. nos ayudará a conocer cuantos duplicados tiene nuestro índice. Recuerda que Total dup puede mostrarte una cantidad incorrecta de duplicados si no has hecho previamente un ciclo backup/restore.

Max dup es la cantidad máxima de valores duplicados que tiene un índice.

Fill distribution es una tabla de frecuencias y seguramente te recordarás de ellas si alguna vez estudiaste Estadísticas. Hay 5 filas, yendo de 20% en 20%, cada fila indicando la cantidad de páginas de índices que están completadas hasta ese porcentaje.

gstat02

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

En la Captura 6. vemos que hay 8 páginas de índices que están llenas entre un 20% y un 39%. Y hay 3 páginas de índices que están llenas entre un 40% y un 59%. La suma de 8 + 3 es 11, y siempre debe coincidir con leaf buckets.

Esta distribución es mala, estamos usando más páginas que las necesarias. ¿Cómo sabemos eso? porque no hay páginas rellenas entre un 80% y un 99%. En una buena distribución el número mayor debería estar siempre en la última fila, (es decir, en 80% a 99%). ¿Cómo conseguimos eso? Con un ciclo backup/restore y luego usando el backup restaurado.

Artículos relacionados:

Entendiendo a GSTAT (1)

Entendiendo a GSTAT (2)

Entendiendo a GSTAT (3)

El índice del blog Firebird21

El foro del blog Firebird21

Entendiendo a GSTAT (3)

Deja un comentario

Ya hemos visto algunas de las características del programa GSTAT en estos artículos:

Entendiendo a GSTAT (1)

Entendiendo a GSTAT (2)

ahora continuaremos analizándolo.

Opción -data

Esta es la opción que debemos elegir cuando solamente nos interesan las páginas de datos. Como recordarás, en Firebird todo se guarda dentro de páginas. En cada página se guarda solamente una clase de ítems, nunca jamás se mezclan. Así por ejemplo tenemos una página de cabecera, una o varias páginas para las transacciones, una o varias páginas para los datos, una o varias páginas para los índices, etc.

La opción -data nos informa sobre los datos que se encuentran dentro de nuestras tablas. Por ejemplo, si tenemos una tabla llamada ALUMNOS los nombres de los alumnos se encontrarán dentro de una o varias páginas de datos. Si tenemos una tabla llamada PROFESORES los nombres de los profesores se encontrarán dentro de una o varias páginas de datos. Pero las páginas donde se encuentran los nombres de los alumnos son distintas a las páginas donde se encuentran los nombres de los profesores. Es decir, en una página de datos podríamos tener nombres de alumnos, o nombres de profesores, pero jamás de ambos.

Eso significa que una tabla puede tener muchas páginas de datos, y que todas esas páginas le corresponden exclusivamente a esa tabla. Por ejemplo, los nombres de los ALUMNOS podrían encontrarse en las páginas 12.345, 12.346, 20.118, 20.129, y los nombres de los PROFESORES podrían encontrarse en las páginas 11.521 y 12.379, los números son siempre distintos.

gstat01

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

En la Captura 1. invocamos a GSTAT con la opción -data para que nos muestre información sobre los datos contenidos en nuestras tablas y, como es lo normal, enviamos esa información a un archivo de texto para poder analizarlo con más facilidad.

Abrimos ese archivo de texto con el Bloc de Notas del Windows y vemos lo siguiente (Nota: los nombres de nuestras tablas siempre aparecen ordenados alfabéticamente):

gstat02

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

¿Cuál es el significado de lo que estamos viendo?

ASIENTOSCAB es el nombre de nuestra tabla

(131) es el identificador de esta tabla en RDB$RELATIONS, lo cual podemos comprobar facilmente así:

gstat03

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

Como podemos ver en la Captura 3., el identificador 131 le corresponde a la tabla ASIENTOSCAB. En RDB$RELATIONS se guardan los nombres de todas las tablas que tiene la Base de Datos.

Primary pointer page es número de la primera página donde se guardan los punteros a las páginas de datos. ¿Qué significa eso? Bien, como ya sabemos, cada tabla puede tener varias páginas de datos y los números de esas páginas deben guardarse en algún lado. El lugar donde se guardan es en una Pointer Page. O sea que en una Pointer Page se encuentran los números de cada una de las páginas de datos de una tabla. El número 502 que vemos en la Captura 2. significa que en la página 502 se encuentran los números de las páginas de datos de la tabla ASIENTOSCAB. Las tablas grandes pueden necesitar de varias Pointer Page, la Primary pointer page es la primera de esas páginas. Por ejemplo, en la página 502 (que como ya sabemos es una pointer page) podríamos tener los números 31.218, 31.219, 92.128; eso significa que en las páginas cuyos números son 31.218, 31.219, y 92.128 se encuentran datos de la tabla ASIENTOSCAB.

Index root page es el número de la primera página donde se guardan los punteros a los índices. Así como tenemos páginas donde se guardan los datos de una tabla también tenemos páginas donde se guardan los índices de esa tabla. El número 503 que vemos en la Captura 2. significa que en la página número 503 se encuentran los punteros a las páginas de índices de la tabla ASIENTOSCAB. En otras palabras, los números que se encuentren en la página 503 serán los números de las páginas donde se encuentran los índices de la tabla ASIENTOSCAB. Si por ejemplo en la página 503 están los números 14.127 y 29.218 significa que en esas páginas hay índices que corresponden a la tabla ASIENTOSCAB.

Data pages es la cantidad total de páginas de datos de nuestra tabla. En la Captura 2. vemos que la tabla ASIENTOSCAB está usando 148 páginas de la Base de Datos. Hay que tener en cuenta algo importante aquí: algunas de esas páginas pueden contener basura dejada por los comandos UPDATE y DELETE, o tener datos de transacciones que aún no han finalizado con un COMMIT. O sea que no necesariamente todas las 148 páginas que usa ASIENTOSCAB contienen datos útiles.

Data page slots es la cantidad de punteros que hay en las pointer pages y frecuentemente es igual que data pages ¿por qué eso? porque cada página de datos tiene un puntero que guarda ese número de página. Si observamos la Captura 2. veremos que data pages es 148 y que data page slots también es 148. Eso es lo normal, y está muy bien que así sea, significa que la tabla ASIENTOSCAB usa 148 páginas de datos y que hay 148 punteros indicando cuales son esos números de página. Si los números son diferentes, eso no es un error. ¿qué significa que los números sean diferentes? Cada vez que el motor del Firebird necesita una nueva página para guardar los datos de una tabla guarda el número de esa página en una pointer page de esa tabla. Si más adelante esa página que tenía datos ya no los tiene porque un comando DELETE los borró, el puntero que guardaba  ese número de página permanece en la pointer page, no es borrado de allí. ¿Por qué no es borrado? porque si más adelante la tabla necesita una nueva página de datos usará (de ser posible) una página de datos que ya había usado anteriormente. La finalidad de hacerlo así es que eso acelera el proceso. O sea, es mejor reusar una página de datos vacía que crear una nueva página de datos.

gstat04

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

En la Captura 4. vemos que la página número 502 es una pointer page, que corresponde a la tabla ASIENTOSCAB y que contiene los números de las páginas de datos que tienen datos de la tabla ASIENTOSCAB. O sea que la página número 31.218 es una página de datos y los datos allí contenidos corresponden a la tabla ASIENTOSCAB. Lo mismo se aplica a los demás números que vemos en la segunda columna.

gstat05

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

En la Captura 5. vemos las estadísticas de la tabla CUENTAS y observamos que data pages es distinto que data page slots. ¿Qué significa eso? que alguna vez la tabla CUENTAS tuvo 85 páginas de datos pero ahora solamente está usando 15 de esas páginas, las restantes 70 están vacías, sin datos, pero disponibles para ser re-utilizadas cuando la tabla CUENTAS las necesite. Esas páginas que ahora están vacías alguna vez contuvieron datos pero esos datos fueron borrados con un comando DELETE o cuando se recolectó la basura. Si una página solamente contenía basura la recolección automática de basura o un sweep podrían eliminar a esa basura pero no liberan a la página, esa página sigue perteneciendo a la misma tabla. Cuando un comando INSERT o un comando UPDATE o un comando DELETE necesite de una nueva página de datos para escribir lo que necesita escribir, se usará una de esas páginas de datos que ahora están vacías.

gstat06

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

En la Captura 6. vemos que la tabla CUENTAS tiene una pointer page cuyo número es 498 y que nos indica cuales de sus páginas de datos contienen datos y cuales están vacías. Esas páginas de datos que ahora están vacías se usarán alguna vez si la tabla CUENTAS necesita más páginas de datos. ¿Y cuándo la tabla CUENTAS podría necesitar más páginas de datos? Cuando las páginas que está usando ahora se llenen. Una página podría llenarse porque el comando INSERT ha agregado nuevas filas o porque los comandos UPDATE o DELETE han agregado versiones viejas de una fila. En general, el comando INSERT llena una página hasta un 80% de su capacidad y deja un 20% libre para que los comandos UPDATE y DELETE coloquen allí las versiones antiguas de las filas. ¿Por qué hace eso? Para acelerar el proceso, porque es más rápido guardar las versiones viejas de una fila en la misma página de datos que contiene a esa fila que usar una nueva página de datos. Por ejemplo, si la fila cuyo identificador es 12.345 se encuentra en la página número 32.334 y se hace un UPDATE o un DELETE a la fila con identificador 12.345 lo más conveniente es que las versiones viejas de esa fila también se guarden en la página número 32.334, eso es más rápido que guardar las versiones viejas en otra página de datos.

Mirando los números de data page slots y de data pages podemos saber cuantas páginas de nuestra tabla contienen solamente basura y por lo tanto están disponibles para ser utilizadas. Según la Captura 5. vemos que data page slots es 85 y que data pages es 15, eso implica que hay 70 páginas de datos vacías y disponibles.

Un ciclo backup/restore hace que ambos números sean iguales. En el backup restaurado la cantidad de data pages será siempre igual a la cantidad de data page slots.

Average fills es el promedio de llenado de las páginas de datos, e incluye a las versiones viejas de las filas (las versiones viejas son creadas por los comandos UPDATE y DELETE). Mirando la fill distribution tendremos más detalles.

Fill distribution es una tabla de frecuencias. Si alguna vez estudiaste Estadísticas seguramente las recordarás.

gstat02

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

Esta tabla de frecuencias tiene 5 filas, que van de 20% en 20% y sirve para indicarnos como están llenadas las páginas de datos.

Mirando la Captura 7. descubrimos que hay 97 páginas de datos que están llenas entre un 0% y un 19%, o sea que en esas 97 páginas de datos hay muchísimo espacio libre.

Hay 5 páginas de datos que están llenas entre un 20% y un 39%

Hay 20 páginas de datos que están llenas entre un 40% y un 59%

Hay 3 páginas de datos que están llenas entre un 60% y un 79%

Hay 23 páginas de datos que están llenas entre un 80%  y un 99%, o sea que hay muy poco espacio libre en ellas

La suma de esas cantidades de páginas siempre debe ser igual a data pages. Veamos si es así: 97 + 5 + 20 + 3 + 23 = 148. Está perfecto, así debe ser.

Artículos relacionados:

Entendiendo a GSTAT (1)

Entendiendo a GSTAT (2)

El índice del blog Firebird21

El foro del blog Firebird21

Entendiendo a GSTAT (2)

Deja un comentario

Ya hemos empezado a entender a GSTAT en el artículo:

Entendiendo a GSTAT (1)

así que ahora continuaremos interiorizándonos más sobre este muy útil programa.

Opción -fetch

A veces, estamos trabajando en una computadora pero no estamos solos, hay algunos curiosos cerca nuestro que están mirando lo que estamos haciendo. Desde luego que si escribimos nuestra contraseña podrán verla … y recordarla … y usarla cuando no estemos presentes.

gstat01

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

Claro, podríamos pedirles que desaparezcan, que se vayan a otro lado, que dejen de curiosear, que se dediquen a hacer algo productivo bien lejos de nosotros, etc., pero eso no siempre es posible. Y entonces ¿qué hacemos?

La solución es guardar nuestra contraseña en un archivo de texto (que podría estar en un pen-drive, por ejemplo) y extraer la contraseña desde ese archivo de texto.

gstat02

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

Desde luego que el nombre del archivo puede ser cualquiera, no necesariamente FIREBIRD.TXT, y nuestra contraseña también puede ser cualquiera, no necesariamente contra99. Esto es solamente un ejemplo.

Recuerda que una contraseña que todos conocen es lo mismo que no tener contraseña.

gstat03

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

En la Captura 3. vemos que la contraseña se obtiene desde el archivo de texto FIREBIRD.TXT que se encuentra en el pen-drive colocado en la unidad G: y en la carpeta \SQL

De esta manera, siempre y cuando no dejemos nuestro pen-drive al alcance de los curiosos, ellos no podrán conocer cual es nuestra contraseña.

Esta, es una muy importante medida de seguridad que deberíamos acostumbrarnos a adoptar: nunca escribir la contraseña, siempre obtenerla de un archivo de texto.

gstat04

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

En la Captura 4. estamos viendo la forma correcta de usar al programa GSTAT. Nuestra contraseña la obtenemos de un archivo de texto y la salida del programa GSTAT la enviamos a otro archivo de texto. Esta es la forma mejor y más recomendable.

Artículos relacionados:

Entendiendo a GSTAT (1)

El índice del blog Firebird21

El foro del blog Firebird21

 

 

 

 

 

Entendiendo a GSTAT (1)

Deja un comentario

GSTAT es una herramienta que se usa desde la línea de comandos y su misión es mostrarnos estadísticas sobre una Base de Datos. Deber ser ejecutado en la misma computadora donde se encuentra el Servidor del Firebird y solamente puede ser ejecutado por el usuario SYSDBA o por el usuario que creó la Base de Datos. Como el tema es bastante largo, lo trataremos en varios artículos.

Se lo invoca de la siguiente manera:

GSTAT [opciones] MiBaseDatos

donde MiBaseDatos debe constar de la ruta completa y las opciones disponibles son las siguientes (se pueden abreviar con las letras dentro de los paréntesis):

-(a)ll       Analiza las páginas de datos y las páginas de índices
-(d)ata      Analiza solamente las páginas de datos
-(h)eader    Analiza solamente la página cabecera
-(i)ndex     Analiza solamente las páginas de índices
-(s)ystem    Como -(a)ll pero también incluye estadísticas sobre las tablas internas
-(u)ser      Nombre del usuario
-(p)assword  Contraseña del usuario
-(f)etch     Extrae la contraseña de un archivo de texto
-(r)ecord    Muestra el tamaño y estadísticas de la versión
-(t)able     Solamente analiza las tablas que se especifican aquí
-(tr)usted   Usa autentificación de confianza
-(z)         Muestra la versión de GSTAT

Como la información que muestra GSTAT puede ser muy larga, y ocupar inclusive cientos o miles de líneas, lo recomendable es enviar esa información a un archivo de texto para poder analizarla con mayor facilidad.

Entonces, la forma correcta de invocarlo es así:

gstat01

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

Donde hemos usado > NombreArchivo para indicarle que envíe su salida a un archivo de texto.

En nuestro ejemplo ese archivo de texto se llama TRANSC.TXT, y usamos el Bloc de Notas del Windows para ver su contenido.

gstat02

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

En la Captura 2. se ven solamente las primeras líneas del archivo TRANSC.TXT, en realidad ese archivo es muchísimo más grande, tiene varios cientos de líneas.

Veamos ahora con mayor detenimiento el significado y utilidad de cada una de las opciones que tenemos disponibles:

Opción -header

Podemos abreviarla como -h

gstat03

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

En la Captura 3. invocamos a GSTAT con la opción -h para que nos muestre la información que se encuentra en la cabecera de esta Base de Datos.

gstat04

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

Flags (Banderas)

Este número es siempre cero.

Checksum (Suma de comprobación)

Es siempre 12345 y sirve para comprobar si hay algún error físico en el disco duro. Cuando la página de cabecera es guardada en el disco y más tarde leída el checksum de la página de cabecera es comparado con 12345 y si son distintos, entonces un error de checksum es mostrado.

Generation (Generación)

Es un contador que se incrementa cada vez que se escribe algún dato en la página de cabecera.

Page size (tamaño de la página)

Es el tamaño que tiene cada una de las páginas de la Base de Datos, en bytes.

ODS version (versión de on-disk-structure)

El número de versión de on-disk-structure, sirve para conocer con cual versión del Firebird se creó la Base de Datos.

10.0 = 1.0
10.1 = 1.5
11.0 = 2.0
11.1 = 2.1
11.2 = 2.5
12.0 = 3.0

Oldest transaction (la transacción más antigua)

El identificador de la más antigua transacción «interesante». Una transacción es «interesante» cuando no ha finalizado con un COMMIT. Se le abrevia como OIT.

Oldest active (la más antigua transacción activa)

El identificador de la más antigua transacción activa. Una transacción está activa cuando no ha finalizado ni con un COMMIT ni con un ROLLBACK y no está en el limbo. Se la abrevia como OAT.

Oldest snapshot (la más antigua transacción instantánea)

El identificador de la más antigua transacción cuya basura no puede ser recolectada. La basura no será recolectada de esta transacción ni de las transacciones que tengan un identificador mayor. Se la abrevia como OST. La diferencia entre la Oldest snapshot y la Oldest active determina cuando un sweep automático ocurre. El valor por defecto es 20000.

Next transaction (siguiente transacción)

El identificador que se le asignará a la siguiente transacción. Se la abrevia como NT.

Bumped transaction (transacción superada)

 Está obsoleta, ya no se usa. Siempre es 1.

Sequence number (número de secuencia)

Número de secuencia de la página de cabecera. Está obsoleta, ya no se usa. Siempre es 0.

Next attachment ID (siguiente identificador de conexión)

El identificador de la siguiente conexión a esta Base de Datos. Indica cuantas veces se han conectado a esta Base de Datos. Cada vez que una aplicación (cualquier aplicación) se conecta a esta Base de Datos este número aumenta en 1. (La excepción es GSTAT, porque GSTAT no se conecta de la forma normal).

Implementation ID (Identificador de la implementación)

Cuando la Base de Datos fue creada, pudo haber sido creada en una computadora que tenía diferente hardware y sistema operativo que la computadora en la cual se encuentra ahora. El Implementation ID muestra un número que identifica al hardware en el cual la Base de Datos fue creada.

Shadow count (cuenta de espejo)

Aunque en realidad shadow significa sombra, aquí se lo traduce como espejo. Muestra la cantidad de archivos adjuntos a esta Base de Datos o disponibles para ser usados por esta Base de Datos. Este número a veces es incorrecto, por eso es preferible escribir SHOW DATABASE en el programa ISQL para tener la información exacta.

Page buffers (buffers de página)

Es la cantidad de páginas que se pueden almacenar en la memoria caché. Un valor 0 significa que se usa el valor predeterminado, que por defecto es 2048 en SuperServer, y 75 en Classic y en SuperClassic. Se puede cambiar ese valor en el archivo FIREBIRD.CONF, en la entrada DefaultDbCachePages. También puede cambiarse con el programa GFIX.

Next header page (siguiente página de cabecera)

El número de página que tiene la siguiente página de cabecera. En general todas las bases de datos tienen una sola página de cabecera y por lo tanto este número es casi siempre 0.

Database dialect (dialecto de la Base de Datos)

Es siempre 3 en las nuevas versiones de Firebird. Anteriormente también podía ser 1, pero el dialecto 1 ya está obsoleto.

Creation date (fecha de la creación)

La fecha en la cual esta Base de Datos fue creada originalmente o la fecha en la cual fue restaurada por el programa GBAK.

Attributes (atributos)

Atributos que puede tener la Base de Datos.

no reserve. Todas las páginas son rellenadas al 100%, no se deja espacio libre en las páginas para INSERT, UPDATE, o DELETE. Es útil solamente en las bases de datos que son de sólo lectura.

force write. Los datos son escritos en el disco duro en el momento en que se solicita, no se guardan en la memoria caché sino que son directamente escritos en el disco. Esto es más lento que guardar los datos en la memoria caché pero es mucho más seguro, sobre todo en Windows.

shutdown. La Base de Datos ha sido cerrada y no puede ser utilizada.

read only. La Base de Datos es de sólo lectura, nada se puede escribir en ella.

multi-user maintenance. La Base de Datos está cerrada para realizar mantenimiento en ella. Solamente puede ser abierta por el usuario SYSDBA o por el creador de la Base de Datos o por ambos.

single-user maintenance. La Base de Datos está cerrada para realizar mantenimiento en ella. Puede ser abierta o por el usuario SYSDA o por el creador de la Base de Datos, pero solamente por uno de ellos, no por ambos.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird2

La forma correcta de realizar el SWEEP

Deja un comentario

Una de las tareas de mantenimiento que se deben realizar en las bases de datos, especialmente en las que tienen tablas con muchos millones de filas, es la del sweep.

Pero ¿cuál es la manera correcta de realizar esa tarea?

Primero, debemos recordar que el sweep se encarga de eliminar toda la basura que se haya acumulado dentro de la Base de Datos a causa de la ejecución de los comandos UPDATE y DELETE. Siempre que se ejecuta el comando UPDATE o el comando DELETE se deja basura dentro de la Base de Datos, sin importar que la transacción haya finalizado con un COMMIT o con un ROLLBACK; por lo tanto, en algún momento deberemos eliminar esa basura.

Segundo, un sweep automático es realizado por el Firebird cada vez que creamos un backup usando el programa GBAK. Por lo tanto, no siempre es necesario realizar un sweep manual, aunque debemos recordar que el sweep automático también puede fallar y por lo tanto también deberemos verificarlo.

Tercero, tanto si realizamos un sweep manual como un sweep automático debemos comprobar que fue completado exitosamente. ¿Por qué? Porque si no fue completado exitosamente entonces dentro de nuestra Base de Datos habrá quedado mucha basura y eso hará que las tareas que se realicen en ella sean mucho más lentas de lo que deberían ser.

Pasos a seguir:

  1. Verificar si es necesario realizar el sweep
  2. Realizar el sweep
  3. Comprobar si el sweep fue completado exitosamente
  4. Si descubrimos que hay problemas, buscar y corregir esos problemas

Paso 1. Verificar si es necesario realizar el sweep

Desde luego que este paso solamente lo hacemos antes de realizar un sweep manual. El programa GSTAT con la opción -h nos da la información que estamos necesitando.

sweep01

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

En la Captura 1. vemos que la diferencia entre la Oldest Transaction y la Next Transaction es muy grande. Eso puede ocurrir normalmente y no está mal que ocurra cuando hay muchos usuarios conectados y trabajando con la Base de Datos. Pero si no hay otros usuarios conectados o hay muy pocos usuarios conectados es un indicador de que hay mucha basura acumulada dentro de la Base de Datos. Una regla rápida que podemos usar es la siguiente: «si hay hasta 10 transacciones faltantes por cada usuario conectado, es aceptable». ¿Por qué? porque en general los usuarios ejecutan muchos comandos INSERT y muchos comandos SELECT, y muy pocos comandos UPDATE o DELETE. Estos dos últimos comandos son los que colocan basura dentro de la Base de Datos. Desde luego que esta regla rápida no se aplica a todos los casos, ya que cada caso es un caso, y habrá aplicaciones que realizan legítimamente muchos UPDATE y muchos DELETE y así en lugar de 10 transacciones faltantes por cada usuario la cantidad podría ser de 100, de 1000, o incluso más.

En el caso de la Captura 1. vemos que la Oldest Transaction es 60325 y que la Next Transaction es 71820, siendo la diferencia entre ellas de 11495, un número muy grande de transacciones faltantes. Como la aplicación que usa esa Base de Datos realiza pocos UPDATE y pocos DELETE entonces es un indicador de que un sweep es requerido.

Paso 2. Realizar el sweep

Un sweep manual se realiza mediante el programa GFIX con la opción -sweep

sweep02

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

Como puedes ver, el programa GFIX no te da información sobre la tarea que realizó. Lo positivo es que si no muestra un mensaje significa que finalizó ok.

Paso 3. Comprobar si el sweep fue completado exitosamente

Después de haber ejecutado el programa GFIX con la opción –sweep debemos conectarnos a nuestra Base de Datos, iniciar una transacción, y finalizar esa transacción con el comando COMMIT. Esto es necesario para que se muevan los identificadores de las transacciones.

 sweep04

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

Desde luego que no es necesario usar el programa ISQL para iniciar una transacción y finalizarla con un COMMIT. Cualquier programa que pueda conectarse a la Base de Datos servirá muy bien. Lo importante es iniciar una transacción y finalizarla con un COMMIT, para así mover los identificadores de las transacciones. Cual programa usar es lo de menos.

sweep05

 

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

En la Captura 4. hemos vuelto a ejecutar el programa GSTAT con la opción -h y así podemos observar que los identificadores de las transacciones están todos ok. Los identificadores están todos ok cuando la diferencia entre ellos es de 1 ó máximo de 2.

Paso 4.  Si descubrimos que hay problemas, buscar y corregir esos problemas

Si los identificadores de las transacciones no están todos ok (o sea, la diferencia entre ellos es más que 2 y no había otros usuarios conectados a la Base de Datos cuando se realizó el sweep), eso significa que el sweep no eliminó a toda la basura. ¿Y por qué no eliminó a toda la basura? La razón más frecuente es que hay una transacción (o más de una transacción) cuyo acceso es READ WRITE (o sea, que la transacción puede escribir en la Base de Datos) y esa transacción hace mucho tiempo que se inició y aún no ha finalizado con un COMMIT ni con un ROLLBACK. Otra razón, mucho más dolorosa, es que la Base de Datos está corrupta.

En estas circunstancias (es decir, cuando los identificadores no están todos ok) debemos ejecutar el programa GFIX con las opciones -validate y -full

sweep06

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

Si queremos conocer cuales son las transacciones que hace mucho tiempo se iniciaron y aún no han finalizado ni con un COMMIT, ni con un ROLLBACK, podemos escribir el siguiente comando:

Listado 1.

SELECT
   MON$TRANSACTION_ID,
   MON$TIMESTAMP
FROM
   MON$TRANSACTIONS
ORDER BY
   MON$TRANSACTION_I

Y para eliminar a la transacción problemática, podemos escribir:

Listado 2.

DELETE FROM
   MON$TRANSACTIONS
WHERE
   MON$TRANSACTION_ID = NúmeroTransacciónProblemática

Si las transacciones problemáticas son varias, entonces podríamos necesitar repetir estos pasos varias veces.

Conclusión:

Tanto si se realiza un sweep manual como un sweep automático, debemos verificar que al finalizar los identificadores de las transacciones estén todos ok. Los identificadores de las transacciones están todos ok cuando la diferencia entre ellos es de 1 ó de 2, cuando no hay otros usuarios conectados a la Base de Datos. Si los identificadores no están ok, entonces debemos validar la Base de Datos, buscar las transacciones que se están demorando mucho en finalizar y eliminar a esas transacciones.

Artículos relacionados:

Entendiendo los identificadores de las transacciones

Entendiendo sweep y garbage collection

El índice del blog Firebird21

El foro del blog Firebird21

Viendo las estadísticas de un backup y de un restore

Deja un comentario

Como sabes, con el programa GBAK puedes realizar el backup de una Base de Datos y también lo puedes restaurar más tarde.

Una interesante opción de ese programa es pedirle que muestre algunas estadísticas de la tarea que esté realizando. Para eso utilizaremos la opción STATISTICS.

La opción STATISTICS puede recibir los siguientes parámetros:

T   para mostrar el tiempo transcurrido desde que se inició el backup o el restore

D   para mostrar el tiempo que demoró finalizar la tarea que se realizó

R   para mostrar la cantidad de páginas de la Base de Datos que fueron leídas

W   para mostrar la cantidad de páginas de la Base de Datos que fueron escritas

Ejemplos:

gbak01

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

En la Captura 1. hacemos un backup de una Base de Datos y pedimos que se nos muestren las estadísticas. El resultado de ejecutar al programa GBAK lo vemos a continuación.

gbak02

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

A continuación de la palabra «gbak:» vemos 4 números, correspondientes a los parámetros T, D, R, W de la opción STATISTICS.

En (1) vemos que escribir en el backup los roles finalizó a los 27 segundos y 324 milésimas de segundo, y que esa tarea demoró 27 milisegundos en finalizar. También vemos que se leyeron 7 páginas y que 0 páginas fueron escritas.

En (2) vemos que realizar el backup completo se demoró 27 segundos y 407 milisegundos; que mostrar las estadísticas demoró 2 milisegundos; que en total se leyeron 2696 páginas; y que en total se escribieron 18 páginas.

gbak03

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

Al restaurar también usamos la opción STATISTICS, tal como podemos ver en la Captura 3.

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

Y en la Captura 4. podemos ver que realizar el restore demoró en total 58 segundos y 248 milisegundos, que mostrar las estadísticas demoró 1 milisegundo, que en total se leyeron 7645 páginas y que se escribieron 5616 páginas.

Conclusión:

Poder ver las estadísticas puede ser muy útil para nosotros, así sabremos cuales tareas son las que demoran más tiempo en completarse y si esa demora es exagerada, buscar alguna solución al problema.

También nos permite responder al cliente que nos dice: «el backup tarda demasiado tiempo en finalizar». Mirando las estadísticas de la Captura 2., en (2) podemos ver cuanto es «demasiado tiempo» para él.

El parámetro T puede ser muy útil para los usuarios y para nosotros. El parámetro D puede ser muy útil para nosotros. Los parámetros R y D sirven de muy poco, en raras ocasiones podrían sernos de utilidad.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

Buscando texto eficientemente dentro de un string grande

1 comentario

Cuando necesitamos buscar un texto dentro de un string podemos usar LIKE ‘%TextoBuscado%’ o también podemos usar CONTAINING. Ambos funcionarán muy bien, pero tienen un problema: si la cantidad de filas es muy grande o la columna donde puede encontrarse el texto que buscamos tiene muchos caracteres, puede ser muy lento, a veces inclusive extremadamente lento.

La idea para este artículo la obtuve de aquí:

https://blog.cincura.net/233577-poor-mans-full-text-using-psql-only-on-firebird/

Listado 1.

SELECT
   *
FROM
   MiTabla
WHERE
   MiColumna LIKE '%Asunción%'

Listado 2.

SELECT
   *
FROM
   MiTabla
WHERE
   MiColumna CONTAINING 'Asunción'

Tanto el SELECT del Listado 1. como el SELECT del Listado 2. harán bien su trabajo, pero como ya dijimos antes, si la cantidad de filas de MiTabla es muy grande o la cantidad de caracteres en MiColumna es muy grande, pueden ser muy lentos. ¿Por qué? Porque el Firebird nunca usará un índice en esos casos y por lo tanto deberá buscar secuencialmente en cada columna de cada fila la palabra que deseamos encontrar.

Si necesitamos gran velocidad en la búsqueda entonces debemos emplear otro aprovechamiento: usar un diccionario de palabras.

La idea es la siguiente: cada palabra que aparece en la columna MiColumna la guardamos en otra tabla, junto con su correspondiente Identificador. A esa columna le definiremos un índice y de esa manera las búsquedas serán rapidísimas.

Desde luego que hacer eso solamente se justificará si las búsquedas realizadas mediante el Listado 1. o el Listado 2. son lentas, en otro caso no vale la pena tomarse la molestia de hacerlo.

Para que nuestra técnica sea más inteligente también debemos tener en cuenta que a veces los usuarios se equivocan al escribir las palabras que están buscando. Por ejemplo, quieren buscar ‘Asunción’ pero escribieron ‘Asuncion’; o sea, sin el acento sobre la letra ‘o’. O escribieron ‘ASUNCION’, o sea todo en mayúsculas; o escribieron ‘Asumción’, o sea que en lugar de la letra ‘n’ pusieron la letra ‘m’.

Entonces lo que haremos será lo siguiente:

  1. Crear un dominio que acepte tanto mayúsculas como mínusculas (case insensitive) y que acepte tanto palabras acentuadas como no acentuadas (accent insensitive)
  2. Crear una tabla donde se guardarán las palabras y en la cual usaremos el dominio creado
  3. Crear un índice normal que usaremos para buscar palabras normales
  4. Crear un índice inverso que usaremos para buscar palabras invertidas
  5. Crear un stored procedure seleccionable que servirá para extraer todas las palabras de un texto
  6. Crear un trigger que ejecutará al stored procedure seleccionable y luego guardará cada palabra distinta en la tabla, junto con el Identificador de la fila donde se encuentra

Paso 1. Crear un dominio

Listado 3.

CREATE DOMAIN D_PALABRAS_CI_AI 
AS VARCHAR(40) 
CHARACTER SET ISO8859_1
COLLATE ES_ES_CI_AI;

Definimos este dominio como VARCHAR(40) porque suponemos que ninguna de las palabras tendrá más de 40 caracteres, si eso pudiera ocurrir entonces tendrías que aumentar el tamaño.

Paso 2. Crear la tabla donde se guardarán las palabras

palabras01

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

Nuestra tabla (bastante apropiadamente) se llama DICCIONARIO porque contendrá una lista de palabras.

En la columna DIC_TABLAX se guardará el nombre de la tabla que contiene las palabras que podríamos buscar. Eso porque podríamos tener varias tablas en las cuales sería útil realizar esta búsqueda.

En la columna DIC_COLUMN se guardará el nombre de la columna que contiene las palabras que podríamos buscar. Eso porque dentro de una misma tabla podríamos tener varias columnas que nos interesan.

En la columna DIC_IDECAB se guardará el Identificador de la fila que corresponde a la tabla DIC_TABLAX. De esa manera podremos saber en cual fila de la tabla DIC_TABLAX se encuentra la palabra buscada.

En la columna DIC_PALABR se guardará la palabra que puede ser buscada.

Paso 3. Crear un índice normal

Listado 4.

CREATE INDEX IDX_DICCIONARIO ON DICCIONARIO COMPUTED BY (DIC_TABLAX || DIC_COLUMN || UPPER(DIC_PALABR)
);

Paso 4. Crear un índice inverso

Listado 5.

CREATE INDEX IDX_DICCIONARIO1 ON DICCIONARIO COMPUTED BY (DIC_TABLAX || DIC_COLUMN || REVERSE(UPPER(DIC_PALABR))
);

Paso 5. Crear un stored procedure seleccionable

Listado 6.

CREATE PROCEDURE SPG_HALLAR_PALABRAS(
   ftcTexto VARCHAR(32765))
RETURNS(
   ftcPalabra TYPE OF D_NOMBRE40)
AS
   DECLARE VARIABLE lnI        SMALLINT;
   DECLARE VARIABLE lnInicio   SMALLINT;
   DECLARE VARIABLE lnLongitud SMALLINT;
BEGIN

   lnI        = 1;
   lnInicio   = 1;
   ftcTexto   = ftcTexto || ' ';
   lnLongitud = CHARACTER_LENGTH(ftcTexto);

   WHILE (lnI <= lnLongitud) DO BEGIN
      IF(CAST(SUBSTRING(ftcTexto FROM lnI FOR 1) AS D_PALABRAS_CI_AI) NOT SIMILAR TO '[[:ALNUM:]]' AND POSITION(SUBSTRING(ftcTexto FROM lnI FOR 1) IN 'áéíóúñÁÉÍÓÚÑ') = 0) THEN BEGIN
         IF(lnI > lnInicio) THEN BEGIN
            ftcPalabra = SUBSTRING(ftcTexto FROM lnInicio FOR lnI - lnInicio);
            SUSPEND;
         END
         lnInicio = lnI + 1;
      END
      lnI = lnI + 1;
   END
END;

El stored procedure SPG_HALLAR_PALABRAS hallará cada una de las palabras contenidas en el texto que se le envíe como parámetro de entrada. Veamos algunos ejemplos:

Listado 7.

SELECT
   *
FROM
   SPG_HALLAR_PALABRAS('Hoy es un día lluvioso')

Listado 8.

SELECT
   *
FROM
   SPG_HALLAR_PALABRAS('        Hoy es un día lluvioso')

Listado 9.

SELECT
   *
FROM
   SPG_HALLAR_PALABRAS('       Hoy         es         un día lluvioso')

Listado 10.

SELECT
   *
FROM
   SPG_HALLAR_PALABRAS('   Hoy es un        día lluvioso            ')

Tanto si ejecutamos el Listado 7., como el Listado 8., como el Listado 9., como el Listado 10., siempre obtendremos el mismo resultado, aún cuando a la frase original se le hayan agregado espacios en blanco al principio, en el medio, y al final del texto:

palabras02

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

Paso 6. Crear un trigger 

Listado 11.

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

   -- Se borran las filas que se habían insertado correspondientes a este Producto

   IF (UPDATING OR DELETING) THEN
      DELETE FROM
         DICCIONARIO
      WHERE
         DIC_TABLAX = 'PRODUCTOS' AND
         DIC_COLUMN = 'PRD_NOMBRE' AND
         DIC_IDECAB = OLD.PRD_IDENTI;

   -- Se insertan las nuevas filas, una fila por cada palabra de la columna PRD_NOMBRE

   IF (INSERTING OR UPDATING) THEN BEGIN
      INSERT INTO DICCIONARIO (DIC_TABLAX, DIC_COLUMN, DIC_IDECAB, DIC_PALABR)
         SELECT 'PRODUCTOS', 'PRD_NOMBRE', NEW.PRD_IDENTI, ftcPALABRA FROM SPG_HALLAR_PALABRAS(NEW.PRD_NOMBRE);

   END

END;

Para cada tabla que nos interese deberemos crear un trigger similar al mostrado en el Listado 11., de esa manera cada vez que se realice un INSERT, un UPDATE, o un DELETE a alguna fila de esa tabla que nos interesa, se actualizará también la tabla DICCIONARIO.

En el Listado 11. la tabla que nos interesa se llama PRODUCTOS, y dentro de esa tabla la columna que nos interesa se llama PRD_NOMBRE.

Eso significa que cada una de las palabras que coloquemos en la columna PRD_NOMBRE se insertará en la tabla DICCIONARIO para que podamos buscarla muy rápidamente.

Agregando filas a la tabla DICCIONARIO

Para verificar que todo funciona bien le agregaremos algunas filas a la tabla PRODUCTOS y al hacerlo también le estaremos agregando filas a la tabla DICCIONARIO.

Listado 12.

INSERT INTO PRODUCTOS (PRD_CODIGO, PRD_NOMBRE) VALUES('CC267', 'COCA COLA DE 1 LITRO RETORNABLE');
INSERT INTO PRODUCTOS (PRD_CODIGO, PRD_NOMBRE) VALUES('CP389', 'CERVEZA PILSEN DE 750 C.C.');
INSERT INTO PRODUCTOS (PRD_CODIGO, PRD_NOMBRE) VALUES('LT224', 'Leche Trébol de 1 litro descremada');
INSERT INTO PRODUCTOS (PRD_CODIGO, PRD_NOMBRE) VALUES('CB357', 'Cerveza BUDWEISER 66 DE 1 ÑITRO');

Al insertar esas filas a la tabla PRODUCTOS nuestra tabla DICCIONARIO quedó así:

palabras03

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

Verificando que funciona

Ahora que tenemos todo listo lo único que nos falta comprobar es que funcione bien.

Mirando la Captura 3. podemos ver que la palabra «LITRO» está escrita en mayúsculas, en minúsculas, y también mal escrita (en la fila 24 dice «ÑITRO» en lugar de «LITRO»).

Entonces, ¿cómo podemos obtener rápidamente las 3 filas donde se encuentra la palabra ‘LITRO’?

Listado 13.

SELECT
   *
FROM
   DICCIONARIO
WHERE
   DIC_TABLAX || DIC_COLUMN || UPPER(DIC_PALABR) STARTING WITH 'PRODUCTOS' || 'PRD_NOMBRE' || 'LITRO' OR
   DIC_TABLAX || DIC_COLUMN || REVERSE(UPPER(DIC_PALABR)) STARTING WITH 'PRODUCTOS' || 'PRD_NOMBRE' || REVERSE('ITRO') OR
   DIC_TABLAX || DIC_COLUMN || REVERSE(UPPER(DIC_PALABR)) STARTING WITH 'PRODUCTOS' || 'PRD_NOMBRE' || REVERSE('TRO') OR
   DIC_TABLAX || DIC_COLUMN || REVERSE(UPPER(DIC_PALABR)) STARTING WITH 'PRODUCTOS' || 'PRD_NOMBRE' || REVERSE('RO')

Si ejecutamos el Listado 13., obtendremos:

palabras04

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

O sea, que tenemos todas las apariciones de la palabra ‘LITRO’ aún aquella que está mal escrita. Justamente para eso sirven las 3 últimas condiciones puestas en la cláusula WHERE, aquellas que usan la función REVERSE(). En el primer caso escribimos ‘ITRO’, eso significa que la primera letra puede estar mal escrita. En el segundo caso escribimos ‘TRO’, eso significa que las primeras dos letras pueden estar mal escritas. En el tercer caso escribimos ‘RO’, eso significa que las primeras tres letras pueden estar mal escritas.

¿Y qué tan eficiente es nuestro SELECT?

Veamos el PLAN que ejecutó el Firebird para saberlo.

palabras05

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

Como se puede ver en la Captura 5. para la primera línea de la cláusula WHERE usará el índice IDX_DICCIONARIO, y para la segunda, la tercera, y la cuarta líneas, usará el índice IDX_DICCIONARIO1.

O sea, exactamente lo que queríamos conseguir.

Veamos ahora la eficiencia del Listado 13. en forma gráfica.

palabras06

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

Está perfecto. Hay 3 filas que cumplen con la condición impuesta en la cláusula WHERE y hay 3 filas extraídas usando los índices.

Conclusión:

Normalmente podemos usar LIKE ‘%MiTextoBuscado%’ o CONTAINING ‘MiTextoBuscado’ cuando queremos obtener las filas que tienen a ‘MiTextoBuscado’ en ellas. Sin embargo, hay ocasiones en que usar dichas sub-cláusulas puede ser extremadamente lento: funciona bien, pero son muy lentas.

Para esos casos lo conveniente es tener un diccionario de palabras, que servirá para indicarnos en cuales filas está el texto que buscamos.

En este artículo hemos visto un método que podemos utilizar para conseguir nuestro objetivo: búsquedas muy rápidas del texto, aunque la tabla tenga muchísimas filas o aunque tenga columnas que contienen muchísimas palabras.

Artículos relacionados:

https://blog.cincura.net/233577-poor-mans-full-text-using-psql-only-on-firebird/

La función REVERSE()

El índice del blog Firebird21

El foro del blog Firebird21

 

 

Validando números escritos en distintos formatos

Deja un comentario

Tenemos el siguiente problema: en una tabla hay una columna donde se  guardan números de teléfono, los cuales pueden estar repetidos y queremos que haya una sola fila por cada número de teléfono. Pero el problema es que esos números de teléfono pueden estar escritos de varias maneras, por ejemplo:

  • 11-555-9090
  • (11)555-9090
  • 115559090

 

validar01

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

En la Captura 1. vemos el Identificador y el Número de Teléfono que existen en siete filas de una tabla. Solamente el Número de Teléfono de la fila que tiene Identificador igual a 4 no está repetido. En todas las demás filas hay simplemente variaciones de los mismos números, escritos con diferentes formatos.

Entonces, ¿cómo podemos saber cuales filas tienen números de teléfono repetidos?

Primero, buscaremos cuales son los números repetidos, estén escritos en el formato que sea.

Para ellos usaremos la función REPLACE() la cual nos permite reemplazar una carácter (o varios caracteres) por otro carácter (o por varios caracteres). En nuestro caso reemplazaremos por un carácter vacío.

Listado 1.

SELECT
   REPLACE(ALU_TELEFO, '(', '')
FROM
   ALUMNOS

Al ejecutar el Listado 1. veremos algo como:

validar02

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

Si observas la Captura 2. notarás que han desaparecido los paréntesis izquierdos. Seguimos refinando nuestro SELECT.

Listado 2.

SELECT
   REPLACE(REPLACE(ALU_TELEFO, '(', ''), ')', '')
FROM
   ALUMNOS

validar03

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

Si observas la Captura 3. notarás que han desaparecido los paréntesis izquierdos y también los paréntesis derechos. Seguimos refinando nuestro SELECT.

Listado 3.

SELECT
   REPLACE(REPLACE(REPLACE(ALU_TELEFO, '(', ''), ')', ''), '-', '')
FROM
   ALUMNOS

validar04

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

Como ya habrás visto, en la Captura 4. no hay ni paréntesis ni guiones. Así que buscar duplicados ahora ya es más fácil.

Listado 4.

SELECT
   REPLACE(REPLACE(REPLACE(ALU_TELEFO, '(', ''), ')', ''), '-', '')
FROM
   ALUMNOS
GROUP BY
   REPLACE(REPLACE(REPLACE(ALU_TELEFO, '(', ''), ')', ''), '-', '')
HAVING
   COUNT(*) >= 2

El SELECT del Listado 4. también podríamos haberlo escrito de forma más simplificada así:

Listado 5.

SELECT
   REPLACE(REPLACE(REPLACE(ALU_TELEFO, '(', ''), ')', ''), '-', '')
FROM
   ALUMNOS
GROUP BY
   1
HAVING
   COUNT(*) >= 2

En ambos casos obtendremos el mismo resultado:

validar05

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

Donde podemos ver los números que se repiten. Ahora bien, ya sabemos cuales son los números que se repiten, pero ¿en cuáles filas están? Esa información la obtendremos mediante el siguiente SELECT

Listado 6.

SELECT
   ALU_IDENTI,
   ALU_TELEFO
FROM
   ALUMNOS
WHERE
   REPLACE(REPLACE(REPLACE(ALU_TELEFO, '(', ''), ')', ''), '-', '') IN
      (SELECT
         REPLACE(REPLACE(REPLACE(ALU_TELEFO, '(', ''), ')', ''), '-', '')
       FROM
          ALUMNOS
       GROUP BY
          1
       HAVING
          COUNT(*) >= 2)

validar06

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

Donde la única fila que no aparece es la que tiene Identificador igual a 4, porque esa fila no tiene un Número de Teléfono que esté repetido.

Como seguramente te habrás dado cuenta mirando los SELECTs anteriores, usamos una función REPLACE() por cada carácter que deseamos extraer. Un carácter, una función REPLACE(); dos caracteres, dos funciones REPLACE(); tres caracteres, tres funciones REPLACE().

Supongamos ahora que queremos dejar en nuestra tabla solamente la primera fila de cada número. O sea que quisiéramos obtener esto:

validar07

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

¿Cómo lo podemos conseguir?

Listado 7.

DELETE FROM
   ALUMNOS T1
WHERE
   NOT EXISTS (SELECT
                  T2.ALU_IDENTI
               FROM
                  ALUMNOS T2
               WHERE
                  REPLACE(REPLACE(REPLACE(T1.ALU_TELEFO, '(', ''), ')', ''), '-', '') = REPLACE(REPLACE(REPLACE(T2.ALU_TELEFO, '(', ''), ')', ''), '-', '') AND
                  T1.ALU_IDENTI > T2.ALU_IDENTI)

Conclusión:

Hay varias técnicas que podemos usar para conocer si hay números duplicados aunque estén escritos con diferentes formatos, y para borrarlos en caso de necesidad. En este artículo se mostró una de esas técnicas, la cual al autor le parece mucho más sencilla que, por ejemplo, escribir un stored procedure que realice la misma tarea.

Artículos relacionados:

La función REPLACE()

Agrupando por una columna … y viendo todas las demás columnas

Obteniendo la primera fila de cada grupo

El índice del blog Firebird21

El foro del blog Firebird21

 

Listando las funciones externas

Deja un comentario

Como recordarás, Firebird nos permite usar funciones externas en nuestras bases de datos. Una función externa no está incluida en la instalación del Firebird sino que la agregamos después y a cada Base de Datos que la necesite utilizar. Eso significa que cada una de nuestras bases de datos puede tener cero, una, o varias funciones externas.

Si queremos saber cuales son las funciones externas que hemos registrado en una Base de Datos, podemos usar nuestro administrador gráfico para ello:

externa01

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

o podemos escribir un SELECT que nos de esa información:

Listado 1.

SELECT
   *
FROM
   RDB$FUNCTIONS
WHERE
   RDB$SYSTEM_FLAG = 0

Donde obtendremos un resultado similar al siguiente:

externa02

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

  • RDB$FUNCTION_NAME. Es el nombre con el cual se conoce a esta función dentro de nuestra Base de Datos
  • RDB$FUNCTION_TYPE. Tipo de la función, no está siendo usado y por eso siempre es Null
  • RDB$QUERY_NAME. Nombre de la consulta, no está siendo usada y por eso siempre es Null
  • RDB$DESCRIPTION. Comentarios que podemos escribir para describir lo que hace la función
  • RDB$MODULE_NAME. El nombre que tiene el archivo .DLL o el objeto compartido, en el disco duro u otro dispositivo de almacenamiento
  • RDB$ENTRYPOINT. El nombre que tiene la función externa en el archivo .DLL o en el objeto compartido. No siempre es igual a RDB$FUNCTION_NAME
  • RDB$RETURN_ARGUMENT. El número de posición del argumento que se devuelve, en la lista de argumentos de entrada
  • RDB$SYSTEM_FLAG. Una bandera que indica si la función fue definida internamente o externamente. 0=definida externamente, 1=definida internamente

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

 

 

 

Entendiendo los índices de expresiones

2 comentarios

Los índices sirven para dos cosas:

  1. Mostrar el resultado de los SELECTs ordenados por el criterio que nos interesa
  2. Realizar búsquedas o filtros, para que solamente sean afectadas las filas que cumplan con la condición que establecimos

Lo más común es que los índices estén compuestos por una o más columnas en forma directa. Veamos un ejemplo:

indices01

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

indices02

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

Tenemos una tabla ALUMNOS y para ordenar a los alumnos por APELLIDOS y por NOMBRES podríamos crear un índice como el siguiente:

Listado 1.

   CREATE INDEX IDX_ALUMNOS ON ALUMNOS(ALU_APELLD, ALU_NOMBRE);

Y está muy bien, funcionará perfectamente.

Podríamos escribir entonces un SELECT como el siguiente:

Listado 2.

SELECT
   *
FROM
   ALUMNOS
ORDER BY
   ALU_APELLD,
   ALU_NOMBRE

Y así obtendríamos un resultado como este:

indices03

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

Donde como puedes observar, los resultados aparecen ordenados por ALU_APELLD. Pero si queremos saber la diferencia entre ALU_TOTANO y ALU_TOTCOB no es posible usar un índice normal.

¿Y entonces?

Entonces la solución es crear un índice de expresión.

¿Qué es un índice de expresión?

Un índice en el cual se utiliza una expresión aritmética o una expresión alfanumérica o funciones internas.

Ejemplos de índices de expresión:

Listado 3.

   CREATE INDEX IDX_ALUMNOS2 ON ALUMNOS COMPUTED BY (LEFT(ALU_APELLD, 1) || LEFT(ALU_NOMBRE, 1));

   CREATE INDEX IDX_ALUMNOS3 ON ALUMNOS COMPUTED BY (ALU_TOTANO - ALU_TOTCOB);

Como puedes ver, la diferencia entre el índice creado en el Listado 1. y los índices creados en el Listado 3., es que en estos últimos se escribieron las palabras COMPUTED BY y también se usó la función LEFT() en IDX_ALUMNOS2 y una operación aritmética de resta en IDX_ALUMNOS3.

En todos los índices de expresión se deben escribir las palabras COMPUTED BY, tal como vimos en el Listado 3.

Usando índices de expresión

Algo muy importante a recordar es que cuando usamos índices de expresión debemos usarlos exactamente igual a como los definimos.

Listado 4.

SELECT
   *
FROM
   ALUMNOS
WHERE
   LEFT(ALU_APELLD, 1) || LEFT(ALU_NOMBRE, 1) = 'KM'

En este caso el Firebird usará el índice IDX_ALUMNOS2 porque la expresión escrita en la cláusula WHERE es la misma expresión usada en la definición del índice IDX_ALUMNOS2.

Listado 5.

SELECT
   *
FROM
   ALUMNOS
WHERE
   LEFT(ALU_NOMBRE, 1) || LEFT(ALU_APELLD, 1) = 'MK'

En el SELECT del Listado 5. el Firebird no usará el índice IDX_ALUMNOS2 porque la condición escrita en la cláusula WHERE no es igual a la expresión usada en la definición del índice IDX_ALUMNOS2.

Listado 6.

SELECT
   *
FROM
   ALUMNOS
WHERE
   ALU_TOTANO - ALU_TOTCOB > 1000

En el SELECT del Listado 6. el Firebird usará el índice IDX_ALUMNOS3 porque la condición escrita en la cláusula WHERE es la misma expresión escrita en la definición del índice IDX_ALUMNOS3.

Listado 7.

SELECT
   *
FROM
   ALUMNOS
ORDER BY
   ALU_TOTANO - ALU_TOTCOB

En el SELECT del Listado 7. también se usará el índice IDX_ALUMNOS3 porque la expresión escrita en la cláusula ORDER BY es la misma expresión que se usó en la definición del índice IDX_ALUMNOS3.

Conclusión:

Los índices de expresión pueden ser muy útiles en algunos casos, es bueno saber que contamos con esta herramienta para poder usarla cuando nos haga falta.

Artículos relacionados:

Optimizando un SELECT que compara columnas de la misma tabla (2)

El índice del blog Firebird21

El foro del blog Firebird21