Ejemplo de recursión (7). Números faltantes en una serie

1 comentario

Si en una tabla o en una vista o en un procedimiento almacenado seleccionable, tenemos una columna numérica y queremos saber si están todos los números o si falta alguno, podemos usar la técnica mostrada en este artículo para averiguarlo.

Para ello, mediante recursión crearemos una tabla virtual, que en nuestro ejemplo llamaremos RANGO_NUMEROS. Esa tabla virtual contendrá todos los números que nos interesan, de forma consecutiva. Es decir: 1, 2, 3, 4, 5, 6, 7, 8, 9, …

Desde luego que podemos empezar con cualquier número, no es obligatorio que empecemos con el número 1.

La tabla es virtual porque solamente existe en la memoria de la computadora y mientras dure la ejecución del SELECT principal, no existe dentro de la Base de Datos ni tampoco se guarda en el disco duro, solamente existe hasta que el SELECT principal finaliza, luego … desaparece totalmente.

Listado 1. Un SELECT para averiguar si hay números consecutivos faltantes

WITH RECURSIVE RANGO_NUMEROS AS (

   SELECT
      1 AS NUMERO
   FROM
      RDB$DATABASE

   UNION ALL

   SELECT
      NUMERO + 1 AS NUMERO
   FROM
      RANGO_NUMEROS
   WHERE
      NUMERO <= 36
)

SELECT
   NUMERO
FROM
   RANGO_NUMEROS
LEFT JOIN
   REVALUOSCAB
      ON NUMERO = RVC_IDENTI
WHERE
   RVC_IDENTI IS NULL

Como siempre que usamos recursión, debemos asignar el valor inicial (en nuestro ejemplo, es el número 1, pero puedes elegir otro número si quieres) y un valor final (en nuestro ejemplo, es 36).

El valor final es absolutamente necesario establecerlo porque de lo contrario la recursión continuaría indefinidamente. Bueno, en realidad, hasta que llegues al límite de recursiones permitidas o hasta que la computadora se quede sin memoria RAM.

Siempre que uses recursión debes establecer una condición de salida, es decir, una condición para que la recursión finalice. No tendría sentido de otro modo.

¿Que hicimos en el Listado 1.?

Primero, hemos creado una tabla virtual llamada RANGO_NUMEROS, cuyo contenido es una sola columna, llamada NUMERO, y cuyos valores van desde el 1 hasta el 37 de forma consecutiva, es decir sin que falte algún número. Están todos. Va hasta el 37 porque en el SELECT pusimos NUMERO + 1. Y como en el WHERE pusimos 36, entonces obtendremos un número más, en este caso 37.

Segundo, hemos hecho un LEFT JOIN de nuestra tabla virtual llamada RANGO_NUMEROS con la tabla REVALUOSCAB, la cual tiene los números que queremos verificar.

Tercero, pusimos la condición RVC_IDENTI IS NULL para que solamente nos muestre los números que están en la tabla virtual RANGO_NUMEROS y que no están en la tabla REVALUOSCAB. De esta manera, solamente los números que se encuentren en la tabla virtual RANGO_NUMEROS y que no se encuentren en la tabla REVALUOSCAB obtendremos en el resultado.

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

En la Captura 1. vemos el contenido de la columna RVC_IDENTI de la tabla REVALUOSCAB. Como puedes notar, faltan los números que van del 26 al 36.

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

Después de ejecutar el Listado 1. obtenemos como resultado lo que vemos en la Captura 2., es decir, todos los números faltantes.

Conclusión:

Hay varias técnicas para mostrar los números que faltan en una serie, en este artículo hemos visto una de esas técnicas, la cual emplea recursión. Saber usar recursión puede ayudarte en muchos casos, por lo tanto es muy bueno conocer como usarla.

Artículos relacionados:

Stored procedures recursivos

Entendiendo a las tablas autoreferenciadas

Usando CTE (Common Table Expression)

Otro ejemplo de CTE: ventas semanales

Usando varias CTE en una vista o en un stored procedure

FOR SELECT y tablas CTE

Usando recursividad con CTE

Ejemplo de recursión (1). Filas salteadas

Ejemplo de recursión (2). Numerar filas

Ejemplo de recursión (3). Fechas consecutivas

Ejemplo de recursión (4). Actualizando filas recursivamente

Ejemplo de recursión (5). Saldos acumulados

Ejemplo de recursión (6). Repitiendo las filas

El índice del blog Firebird21

El foro del blog Firebird21

Anuncios

Completa documentación sobre Firebird 2.5 disponible

2 comentarios

Si quieres tener una documentación muy completa sobre Firebird 2.5 y lees inglés, entonces la encontrarás en esta página:

https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25.html

Allí podrás enterarte de prácticamente todo lo que necesites saber.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

Actualizando una tabla con datos de otra tabla

1 comentario

En ocasiones podemos necesitar que en una columna de una tabla se encuentren los mismos datos que se encuentran en una columna de otra tabla.

En Firebird el comando UPDATE no soporta la cláusula JOIN. Así que algo como esto no es posible hacer:

Listado 1.

UPDATE
   Tabla1 T1
JOIN
   Tabla2 T2
   ON T1.Columna1 = T2.Columna2
SET
   T1.Columna3 = T2.Columna4

Sin embargo, esto sí podemos hacer:

Listado 2.

UPDATE
   Tabla1 T1
SET
   T1.Columna3 = (SELECT T2.Columna4 FROM Tabla2 T2 WHERE T1.Columna1 = T2.Columna2 ROWS 1)

El SELECT devolverá a una sola fila y a una sola columna de esa fila. O sea, en otras palabras, obtendremos un único valor. Nos aseguramos que devuelva una sola fila al escribir ROWS 1 y devuelve una sola columna porque en el SELECT se escribió una sola columna. Es por lo tanto perfectamente válido lo que hemos hecho.

Desde luego que dentro del SELECT sí podemos usar JOIN y no solamente a una, sino a varias tablas, no hay problemas con eso. También podemos usar las cláusulas GROUP BY, HAVING, ORDER BY, etc.

De esta manera, aunque el comando UPDATE no soporta la cláusula JOIN hemos obtenido el mismo resultado que si la soportara.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

Firebird 3.0 Quick Start Guide

Deja un comentario

La documentación para empezar a usar rápidamente Firebird 3 se encuentra disponible (en inglés) y la puedes descargar desde:

Firebird 3. Quick Start Guide

En ese documento encontrarás los siguientes temas:

  • Instalación
  • Verificación de que la instalación fue realizada correctamente
  • Ubicaciones por defecto de los componentes de Firebird 3
  • Configuración del Servidor
  • Administración del Servidor
  • Conexiones a bases de datos
  • Backups

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

CloudaIDE framework

2 comentarios

Poder terminar nuestras aplicaciones más rápidamente siempre es beneficioso para nosotros. CloudaIDE framework sirve justamente para eso. Crea aplicaciones “en la nube”. Y es gratis.

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

Importante: Requiere Windows de 64 bits o Linux de 64 bits.

Algo muy bueno es que no tiene restricciones para Firebird. O sea que las funcionalidades que son pagas para MySql, PostgreSql, y Oracle, son gratis para Firebird.

Puede ser descargado desde:

http://cloudaide.org/index.html

En esa página hay también un vídeo explicativo que demuestra lo fácil que es usar a CloudaIDE framework. Y algo muy bueno de ese vídeo es que está basado en una Base de Datos de … Firebird.

Así que si tienes intenciones de desarrollar aplicaciones para “la nube”, este programa puede resultarte de gran ayuda y deberías probarlo.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

¿Cómo funciona una transacción SNAPSHOT?

Deja un comentario

Como recordarás, una transacción en Firebird puede tener uno de estos tres aislamientos:

  • READ COMMITED
  • SNAPSHOT
  • SNAPSHOT TABLE STABILITY

Los aislamientos le dicen al Firebird lo que debe hacer cuando una fila quiere ser actualizada (UPDATE) o borrada (DELETE) por más de una transacción al mismo tiempo.

Veamos un ejemplo:

  1. La transacción T1 empieza (su aislamiento es SNAPSHOT, y su modo de bloqueo es WAIT)
  2. La transacción T2 empieza (su aislamiento es SNAPSHOT)
  3. La transacción T2 actualiza (UPDATE) a una fila X de la tabla PRODUCTOS.
  4. La transacción T2 finaliza con un COMMIT
  5. La transacción T3 empieza (su aislamiento es SNAPSHOT)
  6. La transacción T3 también actualiza (UPDATE) a la misma fila X de la tabla PRODUCTOS
  7. La transacción T1 trata de actualizar (UPDATE) a la misma fila X de la tabla PRODUCTOS, pero como esa fila está bloqueada por la transacción T3 entonces deberá esperar hasta que la transacción T3 finalice.

Sin embargo, debemos notar que en este ejemplo la transacción T1 fallará siempre. ¿Por qué? Porque tendrá un conflicto con la transacción T3 si la transacción T3 finalizó con un COMMIT, o tendrá un conflicto con la transacción T2 si la transacción T3 finalizó con un ROLLBACK. O sea que, sin importar como termine la transacción T3 (con un COMMIT o con un ROLLBACK) la transacción T1 fallará.

Te puedes preguntar: ¿y por qué la transacción T1 debe esperar hasta que finalice la transacción T3? Después de todo, en ambos casos fallará, entonces ¿por qué la espera?

La respuesta está en que el Firebird solamente verifica la última versión de una fila para saber si ocurrió un conflicto o no. Si verificara la anteúltima versión entonces podría hacer fallar a la transacción T1 en el mismo momento en que hiciera un UPDATE, pero eso implicaría más trabajo y por lo tanto solamente verifica a la última versión.

Una transacción SNAPSHOT en el momento en que se inicia copia en su porción de la memoria RAM de la computadora la TIP (Transaction Inventory Page) conteniendo a todas las transacciones que están activas en ese momento. O sea, la transacción T2 conoce cuales son todas las transacciones que estaban activas cuando se inició la transacción T2, pero desconoce totalmente a las transacciones que se iniciaron después que ella y por lo tanto supone que están activas ya que evidentemente no habían finalizado cuando empezó la transacción T2. Cada transacción tiene un número único, que se guarda en la TIP. Cuando una transacción inicia bloquea a su propio número y lo desbloquea cuando finaliza (sea con un COMMIT o con un ROLLBACK). De esta manera es muy fácil saber si una transacción está activa o no. Si no se puede desbloquear su número, está activa. Si se puede desbloquear su número, no está activa.

Cuando una transacción SNAPSHOT trata de bloquear a una fila para hacerle un UPDATE o un DELETE, lo que puede ocurrir es lo siguiente:

  • Si la última versión de esa fila fue creada por una transacción que tiene un número menor y que no está en su copia de la TIP, el bloqueo tendrá éxito. ¿Por qué? porque como no está en su copia de la TIP y el número es menor, significa que una transacción anterior que insertó o actualizó a la fila ya finalizó con un COMMIT.
  • Si la última versión de una fila fue creada por una transacción cuyo número está en la TIP entonces debe verificar si esa transacción ya finalizó. ¿Y cómo lo verifica? Tratando de bloquear el número que esa otra transacción tiene en la TIP. Si lo consigue, la otra transacción ya finalizó y se puede bloquear a la fila con éxito.
  • Si el último COMMIT a la fila fue realizado por una transacción que tiene un número de transacción mayor, eso significa que esa otra transacción empezó después. Y por lo tanto, no se podrá bloquear a la fila.

Ejemplo:

Empieza una transacción, su número es 529, y en su copia de la TIP tiene a los números 521, 525, 526, 528. Eso significa que esas 4 transacciones están activas, aún no han finalizado ni con un COMMIT ni con un ROLLBACK.

La transacción cuyo número es 529 quiere hacer un UPDATE a una fila X de la tabla PRODUCTOS, el número de transacción que tiene la última versión de esa fila X es el 291. Como el número de transacción 291 no está en la copia de la TIP, eso significa que la transacción 291 (o una transacción anterior a ella) ya ha finalizado con un COMMIT y por lo tanto se podrá realizar el UPDATE con éxito a la fila X.

La transacción cuyo número es 529 quiere hacer un UPDATE a una fila X de la tabla PRODUCTOS, el número de transacción que tiene la última versión de esa fila es el 526. Como el número de transacción 526 está en la copia de la TIP, eso significa que la transacción 526 estaba activa cuando se inició la transacción 529. Pero ¿está activa ahora? quizás sí, quizás no, para verificarlo la transacción 529 trata de bloquear al número 526 en la TIP global, no en su propia copia de la TIP. Si consigue realizar el bloqueo, la transacción 526 ya no está activa y entonces podrá realizar el UPDATE con éxito. ¿Y si no consigue bloquear, qué hace? Eso dependerá del modo de bloqueo. Si es WAIT, seguirá intentando bloquear hasta tener éxito. Si es NO WAIT lanzará una excepción con un mensaje de error.

La transacción cuyo número es 529 quiere hacer un UPDATE a una fila X de la tabla PRODUCTOS, el número de transacción que tiene la última versión de esa fila es el 540. ¿Podrá la transacción 529 realizar el UPDATE? Depende. Si la transacción 540 finaliza con un COMMIT, no podrá. ¿Por qué no? Porque 540 es mayor que 529. Si la transacción 540 finaliza con un ROLLBACK entonces hay que buscar el número que tiene la última versión de esa fila X cuya transacción finalizó con un COMMIT. Si el último COMMIT a la fila X fue realizado por la transacción 520, la transacción 529 podrá realizar el UPDATE (porque 520 es menor que 529). Si el último COMMIT a la fila X fue realizado por la transacción 535, la transacción 529 no podrá realizar el UPDATE (porque 535 es mayor que 529).

Una transacción SNAPSHOT solamente puede actualizar (UPDATE) o borrar (DELETE) a las filas creadas por las transacciones que empezaron antes que ella.

Recuerda que el Firebird crea una nueva versión de una fila cada vez que se ejecuta el comando UPDATE o el comando DELETE en esa fila.

Sin importar como finalice la transacción (con un COMMIT o con un ROLLBACK) hay una nueva fila. Esto va creando filas inservibles (se les llama “basura”) y por ese motivo hay que limpiar a la Base de Datos de basura cada cierto tiempo.

Una fila tiene la siguiente forma:

| Nº de Transacción | Columna1 | Columna2 | Columna 3| etc.

Importante: Una transacción T1 (cuyo aislamiento es SNAPSHOT) puede actualizar (UPDATE) o borrar (DELETE) a una fila solamente cuando el Nº de Transacción que realizó el último COMMIT a esa fila es menor que el número de la transacción T1.

Artículos relacionados:

Entendiendo a las transacciones

Entendiendo a los identificadores de las transacciones

Modos de bloqueo de las transacciones

Bloqueos mortales

Lock conflict on no wait transaction. Deadlock

El índice del blog Firebird21

El foro del blog Firebird21

¿Cuál es la mejor manera de almacenar una IP?

4 comentarios

Este artículo es una colaboración de Esteban Herrero, uno de los más antiguos seguidores de este blog. Gracias Esteban.

 

Si tenemos la siguiente IP 192.168.10.21 y quisiera tener un campo para almacenarla, lo más fácil sería crear un campo Char(15), pero podríamos pensar que no todas las IP tienen 15 caracteres, entonces sería mejor utilizar un VarChar(15). Lo cierto es que cada carácter almacenado ocupa  1 byte, haciendo que el almacenamiento de esta Ip lleve 13 bytes o 15 con todos los caracteres.

Pero hay una mejor forma de guardar una Ip y es utilizando un campo BigInt y sólo estaríamos utilizando 64 Bits (8 bytes). Xq no usar un campo Integer, la razón es muy simple es 32 bits y el valor máximo es 2,147,483,647 y nuestro número convertido no entraría.

 

Vamos a un ejemplo:

 

Ip 192.168.10.21

 

11000000 10101000 00001010 00010101 (en binario)

 

Ahora a Convertir:

21 * 1  +

10 * 256 +

168 * 256 * 256 +

192 * 256 * 256 * 256 = 3,232,238,101

Cómo hacerlo en Firebird? Con estos 2 procedimientos:

Execute Procedure SP_INTTOIP(3232238101);

Execute Procedure sp_iptoint(‘192.168.10.21’);

 

CREATE PROCEDURE SP_INTTOIP(

NIP BIGINT)

RETURNS(

IP VARCHAR(15))

AS

DECLARE VARIABLE Octet1 BIGINT;

DECLARE VARIABLE Octet2 BIGINT;

DECLARE VARIABLE Octet3 BIGINT;

DECLARE VARIABLE Octet4 BIGINT;

DECLARE VARIABLE RestoIP BIGINT;

BEGIN

Octet1 = :nIp / 16777216;

RestoIp = :nIp – (Octet1 * 16777216);

Octet2 = RestoIp / 65536;

RestoIp = RestoIp – (Octet2 * 65536);

Octet3 = RestoIp / 256;

Octet4 = RestoIp – (Octet3 * 256);

Ip = Cast(Octet1 as VARCHAR(3)) || ‘.’ || Cast(Octet2 as VARCHAR(3)) || ‘.’ || Cast(Octet3 as VARCHAR(3)) || ‘.’ || Cast(Octet4 as VARCHAR(3));

SUSPEND;

END;

 

CREATE PROCEDURE SP_IPTOINT(

CIP VARCHAR(15))

RETURNS(

NINT BIGINT)

AS

DECLARE VARIABLE lcColumna VARCHAR(4);

DECLARE VARIABLE nCan1 SMALLINT;

DECLARE VARIABLE nFactor BIGINT;

BEGIN

 

nInt = 0;

nFactor = 16777216;

 

WHILE ((Char_Length(cIp) > 0) AND (POSITION(‘.’ in CIP) <> 0)) DO BEGIN

EXECUTE PROCEDURE Parser(cIp, ‘.’) RETURNING_VALUES :lcColumna;

nCan1 = CHAR_LENGTH(:lcColumna) + 1;

cIp = Overlay(cIp placing ” from 1 for nCan1);

cIp = trim(cIp);

nInt = nInt + (Cast(:lcColumna as Smallint) * nFactor);

nFactor = nFactor / 256;

END

nInt = nInt + CAST(cIp as SMALLINT);

SUSPEND;

END;

 

CREATE PROCEDURE PARSER(

TCTEXTO VARCHAR(18192),

TCSEPARADOR VARCHAR(12))

RETURNS(

FTCNOMBRE VARCHAR(1024))

AS

DECLARE VARIABLE lnPosicion SMALLINT;

BEGIN

lnPosicion = Position(tcSeparador IN tcTexto);

ftcNombre = Left(tcTexto, lnPosicion – 1) ;

END;

 

Older Entries