Crear una tabla por programa

7 comentarios

A veces necesitamos crear una tabla dinámicamente, o sea por programa. Eso significa que dependiendo de los datos que introduzca el usuario o procese nuestro programa serán las columnas que tendrá la tabla.

En este artículo veremos una técnica para conseguir nuestro objetivo.

La tabla que crearemos tendrá dos columnas: una para guardar todas las fechas del rango especificado y otra para guardar si esa fecha corresponde a un feriado o no.

Por supuesto que en tus propias tablas creadas con esta técnica podrás tener muchas más columnas, y también podrás agregarle una Primary Key, Foreign Keys, índices, etc. Este ejemplo es simple y sencillo para mostrar la técnica, la cual podrá servirte de base para crear tablas muy complejas.

Todo lo que debemos hacer es crear un stored procedure que utilice el comando EXECUTE STATEMENT, como se ve a continuación:

CREATE PROCEDURE CREAR_TABLA_FECHAS(
   tcNombreTabla  VARCHAR(28),
   tdFechaInicial DATE,
   tdFechaFinal   DATE)
AS
   DECLARE VARIABLE lcComando VARCHAR(1024);
   DECLARE VARIABLE ldFecha   DATE;
BEGIN

   lcComando = 'CREATE TABLE ' || tcNombreTabla ||
             ' (TAB_FECHAX DATE,
                TAB_FERIAD CHAR(1))';

   EXECUTE STATEMENT :lcComando WITH AUTONOMOUS TRANSACTION;

   ldFecha = tdFechaInicial;

   WHILE (ldFecha <= tdFechaFinal) DO BEGIN
      lcComando = 'INSERT INTO ' || tcNombreTabla ||
                  ' (TAB_FECHAX, TAB_FERIAD)
           VALUES (''' || ldFecha || ''', ' || '''N'')';
      EXECUTE STATEMENT lcComando;
      ldFecha = ldFecha + 1;
   END

END;

Entonces, para ejecutar a este stored procedure escribiríamos algo como:

EXECUTE PROCEDURE CREAR_TABLA_FECHAS('Fechas2014', '01/JAN/2014', '31/DEC/2014')

Y este será el resultado que obtendremos:

CREAR1

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

Una tabla, llamada Fechas2014 (porque ese es el nombre que especificamos) que contiene dos columnas: TAB_FECHAX para guardar las fechas y TAB_FERIAD para guardar si la fecha corresponde a un feriado o no.

Y si revisamos el contenido de esa tabla, veremos algo como esto:

CREAR2

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

hay más filas, porque pedimos que tenga todas las fechas desde el 1 de enero de 2014 hasta el 31 de diciembre de 2014 y eso justamente es lo que tiene la tabla. Desde luego que aquí se muestran solamente algunas de esas filas.

Lo interesante es que la tabla fue creada por el stored procedure y también todas sus filas fueron insertadas por el stored procedure, el usuario no insertó esas filas.

Conclusión:

Usando el comando EXECUTE STATEMENT podemos crear, modificar, o borrar tablas dinámicamente, también insertarles filas, modificarlas, borrarlas, etc. Esto puede ser muy útil en muchas ocasiones pero debemos evaluar si vale la pena o no. ¿Por qué? primero, porque EXECUTE STATEMENT es lento, ya que recién en tiempo de ejecución el Firebird puede analizar la instrucción y segundo porque el Firebird puede detectar cualquier error que hayamos escrito solamente en tiempo de ejecución.

En este ejemplo hemos creado una tabla conteniendo dos columnas. Por supuesto que las tablas pueden tener muchas más columnas y también Primary Key, Foreign Keys, índices, etc. Todo puede hacerse con EXECUTE STATEMENT, por lo tanto debemos aprender a usarlo correctamente.

Artículos relacionados:

EXECUTE STATEMENT

El índice del blog Firebird21

Hallar los movimientos ocurridos entre dos fechas (otro método)

5 comentarios

En este artículo ya habíamos visto un método para hallar todos los movimientos (compras, ventas, cobranzas, pagos, etc.) que ocurrieron entre dos fechas dadas, y si en una fecha no hubo movimientos entonces mostrarla igual pero con un total de cero.

Hallando todas las ventas entre dos fechas dadas

Ahora, veremos otro método con el cual podremos obtener los mismos resultados, gracias a la colaboración de Claudio Martín.

Aquí, lo que haremos será crear un stored procedure seleccionable que nos devolverá todas las fechas de un rango dado, y luego cuando las necesitemos mediante un LEFT JOIN o un RIGHT JOIN las obtendremos.

Este método tiene la ventaja de que el stored procedure seleccionable que escribimos es uno solo y lo podemos utilizar en multitud de ocasiones diferentes, todas las veces que necesitemos un rango de fechas.


CREATE PROCEDURE RANGO_FECHAS(
      tdFecIni DATE,
      tdFecFin DATE)
   RETURNS(
      ftdFecha DATE)
AS
   DECLARE VARIABLE ldFecha DATE;
BEGIN

   ldFecha = tdFecIni;

   WHILE (ldFecha <= tdFecFin) DO BEGIN
      ftdFecha = ldFecha;
      SUSPEND;
      ldFecha = ldFecha + 1;
   END

END;

Entonces, usando la misma tabla que en el artículo anterior, escribiríamos:

SELECT
   R.ftdFecha,
   SUM(COALESCE(M.MVC_TOTALX, 0)) AS TOTAL_VENTAS_DIA
FROM
   RANGO_FECHAS('01/01/2014', '01/07/2014') R
LEFT JOIN
   MOVIMCAB M
      ON R.ftdFecha = M.MVC_FECHAX
GROUP BY
   R.ftdFecha

Y obtendríamos este resultado:

VENTAS1

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

El cual, como puedes verificar, es exactamente igual al obtenido con el otro método. Pero la ventaja de este nuevo método es que el stored procedure seleccionable podemos usarlo siempre que necesitemos obtener todas las fechas de un rango dado.

 Conclusión:

Una de las muchas cosas buenas que tiene el Firebird es que nos permite obtener los mismos resultados usando métodos diferentes, queda a nuestro criterio elegir el que nos parece más conveniente para cada situación particular.

En este caso, podemos tener un stored procedure que cuando las necesitemos nos devolverá todas las fechas de un rango, para ello simplemente lo juntamos mediante LEFT JOIN o RIGHT JOIN a la otra tabla y listo, ya está.

Artículos relacionados:

Hallando todas las ventas entre dos fechas dadas

Entendiendo a los Stored Procedures

El índice del blog Firebird21

 

Hallando todas las ventas entre dos fechas dadas

6 comentarios

En ocasiones podríamos necesitar ver todos los movimientos (compras, ventas, cobranzas, pagos, etc.) que ocurrieron entre dos fechas dadas, pero queremos que si en una fecha no hubo movimientos nos muestre cero.

Eso no podemos resolverlo con un SELECT porque el SELECT solamente nos mostrará los movimientos ocurridos, y si una fecha no tuvo movimientos entonces no será mostrada.

La solución es escribir un stored procedure seleccionable, el cual nos dará la información que necesitamos.

Ejemplo. Ver todas las ventas realizadas entre los días 01/ENE/2014 y 07/ENE/2014

Nuestra tabla MOVIMCAB (donde registramos la cabecera de los movimientos) tiene estos datos (y varios más que ahora no nos interesan):

VENTAS1

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

Escribimos este stored procedure:

CREATE PROCEDURE VENTAS_DIARIAS(
      tdFecIni DATE,
      tdFecFin DATE)
   RETURNS(
      ftdFechax DATE,
      ftnTotalx INTEGER)
AS
   DECLARE VARIABLE ldFecha DATE;
BEGIN

   ldFecha = tdFecIni;

   WHILE (ldFecha <= tdFecFin) DO BEGIN
      ftdFechax = ldFecha;
      ftnTotalx = (SELECT SUM(MVC_TOTALX) FROM MOVIMCAB WHERE MVC_FECHAX = :ldFecha);
      ftnTotalx = COALESCE(ftnTotalx, 0);
      SUSPEND;
      ldFecha = ldFecha + 1;
   END

END;

Y como es un stored procedure seleccionable (sabemos eso porque tiene el comando SUSPEND dentro suyo) lo ejecutamos así:

SELECT
   *
FROM
   VENTAS_DIARIAS('01/01/2014', '01/07/2014')

Para que nos muestre todas las ventas ocurridas entre los días 1 de enero de 2014 y 7 de enero de 2014, agrupadas por fecha. Y este es el resultado que obtenemos:

VENTAS2

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

Donde, como puedes ver, se muestran todas las fechas del rango elegido, si en una fecha hubo ventas el total de las ventas de esa fecha y si no hubo ventas, entonces cero.

Artículos relacionados:

Entendiendo a los Stored Procedures

El índice del blog Firebird21

 

Multiple rows in singleton select

2 comentarios

Este mensaje de error traducido significa: «hay muchas filas en el SELECT y debería haber solamente una»

Y ocurre cuando el Firebird está esperando que un SELECT devuelva una y solamente una fila pero el SELECT está devolviendo más de una fila.

Ejemplo:

UPDATE
   MiTabla
SET
   MiColumna = (SELECT MiOtraColumna FROM MiOtraTabla WHERE MiCondición)

¿Cuál es la solución?

El problema está en el SELECT entonces es allí donde se debe solucionar. Lo más sencillo es ejecutar solamente ese SELECT, ver cual es el conjunto resultado que devuelve y modificar el SELECT para que solamente devuelva una fila.

Para que un SELECT devuelva una fila y solamente una fila tenemos varias alternativas:

  1. Cambiar la condición MiCondición
  2. Usar FIRST 1
  3. Usar ROWS 1
  4. Usar DISTINCT

Conclusión:

El error ocurre cuando un SELECT devuelve más de una fila y debería devolver solamente una fila. La solución es revisar el SELECT para comprobar que es lo que devuelve y luego modificarlo para que devuelva solamente una fila.

Artículo relacionado:

El índice del blog Firebird21

 

Usando SIMILAR TO

2 comentarios

Los predicados de comparación son muy útiles cuando queremos buscar columnas que cumplen con alguna condición, como hemos visto aquí:

https://firebird21.wordpress.com/2014/04/27/los-predicados-de-comparacion/

De todos ellos, el más poderoso es SIMILAR TO.

Pero justamente por ser el más poderoso es también el más complicado para entenderlo y para obtener de él el máximo provecho.

Caracteres especiales

Cuando usamos SIMILAR TO hay algunos caracteres que tienen un significado especial, y son los siguientes:

[ ] ( ) | ^ – + * % _ ? { }

y también el carácter de escape, si el carácter de escape fue definido.

Caracteres comunes

Si no hay caracteres especiales ni carácter de escape entonces SIMILAR TO funciona igual que el operador «=»

'NAPOLEON' SIMILAR TO 'NAPOLEON'               -- Verdadero
'NAPOLEON BONAPARTE' SIMILAR TO 'NAPOLEON'     -- Falso
'NAPOLEON' SIMILAR TO 'NAPOLEON BONAPARTE'     -- Falso
'NAPOLEON' SIMILAR TO 'Napoleón'               -- Puede ser, depende del COLLATE utilizado

Comodines

Se puede usar el comodín _ que reemplaza a un carácter cualquiera, y el comodín % que reemplaza a cualquier cantidad de caracteres.

'NAPOLEON' SIMILAR TO 'N_POLEON'         -- Verdadero
'NAPOLEON' SIMILAR TO 'N_LEON'           -- Falso
'NAPOLEON' SIMILAR TO 'N%LEON'           -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPO%LEON%'       -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPOOO%LEON'      -- Falso
'NAPOLEON' SIMILAR TO 'NAPOOO%LEON%'     -- Falso

El primer caso es verdadero porque el segundo caracter podía ser cualquiera, incluyendo por supuesto a una A

El segundo caso es falso porque el guión bajo reemplaza a solamente un caracter y allí faltarían dos más

El tercer caso es verdadero porque el % reemplaza a cualquier cantidad de caracteres

El cuarto caso es verdadero porque el % también reemplaza al string vacío

El quinto caso es falso porque hay dos letras O sobrantes

El sexto caso es falso porque hay dos letras O sobrantes

Clases de caracteres

Un grupo de caracteres rodeados por corchetes se llama clase de caracteres. Hay coincidencia cuando un carácter, y solamente uno, de los que están rodeados por corchetes es igual.

'NAPOLEON' SIMILAR TO 'NAPO[LMN]EON'           -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPO[LE]ON'             -- Falso
'NAPOLEON' SIMILAR TO 'NAPO[LMN][AEIOU]ON'     -- Verdadero

El primer caso es verdadero porque la L se encuentra en la clase.

El segundo caso es falso porque solamente se puede usar un carácter de la clase y en este caso se necesitaría de dos.

El tercer caso es verdadero porque de la primera clase se extrae la L y de la segunda clase se extrae la E.

Rangos

Usar clases de caracteres es muy conveniente pero cuando los caracteres son muchos puede ser muy tedioso escribirlos a todos. Por eso se pueden usar rangos. Los rangos están compuestos por un carácter inicial, un guión, y un carácter final. Todos los caracteres que se encuentren entre ese carácter inicial y ese carácter final, ambos incluidos, estarán dentro de la clase.

'NAPOLEON' SIMILAR TO 'NAPO[J-R]EON'           -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPO[RSK-PXYZ]EON'      -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPO[AF-JM-SZ]EON'      -- Falso
'NAPOLEON' SIMILAR TO 'NAPO[AF-JLM-SZ]EON'     -- Verdadero

El primer caso es verdadero porque la L está entre las letras J y R.

El segundo caso es verdadero porque la L está entre las letras K y P.

El tercer caso es falso porque la L no está entre la F y la J, tampoco entre la M y la S, y tampoco se la ve en la clase

El cuarto caso es verdadero porque se ve a la L en la clase

Clases predefinidas

Hay algunas clases de caracteres que se utilizan muy frecuentemente entonces están predefinidas, para facilitarnos la vida. Son las siguientes:

[:ALPHA:]

Letras inglesas dentro de los rangos a .. z y A .. Z. Si se utiliza un COLLATE que sea CI entonces también incluye a las vocales acentuadas.

[:DIGIT:]

Los dígitos 0 .. 9

[:ALNUM:]

La unión de [:ALPHA:] con [:DIGIT:]

[:UPPER:]

Letras mayúsculas en el rango A .. Z. También coincide cuando las letras son minúsculas y el COLLATE es CI o es AI

[:LOWER:]

Letras minúsculas en el rango A .. Z. También coincide cuando las letras son mayúsculas y el COLLATE es CI o es AI

[:SPACE:]

El espacio en blanco (código ASCII 32)

[:WHITESPACE:]

Tabulador vertical (código ASCII 9), alimentador de línea (código ASCII 10), tabulador horizontal (código ASCII 11), alimentación de página (código ASCII 12), retorno del carro (código ASCII 13), y espacio en blanco (código ASCII 32).

Usar una clase predefinida es lo mismo que usar todos sus miembros. Las clases predefinidas pueden ser usadas solamente dentro de una definición de clases. Si necesitas verificar la coincidencia contra una clase predefinida y nada más, entonces debes usar dos pares de corchetes.

'NAPOLEON' SIMILAR TO 'NAPO[[:ALPHA:]]EON'      -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPO[[:DIGIT:]]EON'      -- Falso
'NAPOLEON' SIMILAR TO 'NAPO[L[:DIGIT:]]EON'     -- Verdadero
'NAPOLEON' SIMILAR TO '[[:ALPHA:]]'             -- Falso
'N' SIMILAR TO '[[:ALPHA:]]'                    -- Verdadero

El primer caso es verdadero porque la letra L está incluida en la clase predefinida [:ALPHA:]

El segundo caso es falso porque la letra L no está incluida en la clase predefinida [:DIGIT:]

El tercer caso es verdadero porque la L está dentro de la clase

El cuarto caso es falso porque de la clase predefinida [:ALPHA:] solamente se puede usar una letra y NAPOLEON tiene 8 letras

El quinto caso es verdadero porque la letra N está incluida dentro de la clase predefinida [:ALPHA:]

Acento circunflejo

Si se usa un acento circunflejo pueden darse dos casos:

  1. La clase empieza con un acento circunflejo. Siendo así todos los caracteres siguientes son excluidos de la clase
  2. La clase no empieza con un acento circunflejo. Siendo así la clase contiene todos los caracteres anteriores, excepto por los caracteres que se encuentren también después del acento circunflejo
'NAPOLEON' SIMILAR TO 'NAPO[^L]EON'              -- Falso
'NAPOLEON' SIMILAR TO 'NAPO[^A-M]EON'            -- Falso
'NAPOLEON' SIMILAR TO 'NAPO[^A-EL]EON'           -- Falso
'NAPOLEON' SIMILAR TO 'NAPO[^[:DIGIT:]]LEON'     -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPO[A-M^R-V]EON'         -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPO[A-M^J-S]EON'         -- Falso
'NAPOLEON' SIMILAR TO 'NAPO[A-M^L]EON'           -- Falso

 El primero caso es falso porque se excluye a la L

El segundo caso es falso porque se excluye a todos los caracteres que están en el rango A .. M

El tercer caso es falso porque se excluye a todos los caracteres que están en el rango A .. E y también a la L

El cuarto caso es verdadero porque se excluye solamente a los dígitos

El quinto caso es verdadero porque se incluye a todos los caracteres entre A .. M y se excluye a los que están entre R .. V, y la L se encuentra entre los incluidos

El sexto caso es falso porque la L se incluye en el rango A .. M pero se la excluye en el rango J .. S y la exclusión tiene preferencia

El séptimo caso es falso porque la L está en el rango A .. M pero específicamente se la excluye después

Cuantificadores

 Los cuantificadores son los siguientes: ? * +

Además de números encerrados entre llaves

Y los usamos para indicar la cantidad de veces que queremos que se repitan los caracteres

El ? indica que el carácter o clase que le antecede debe ocurrir 0 ó 1 vez

'NAPOLEON' SIMILAR TO 'NAPO?LEON'               -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPOX?LEON'              -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPOXX?LEON'             -- Falso
'NAPOLEON' SIMILAR TO 'NAPO[A-M]?EON'           -- Verdadero
'NAPOLEON' SIMILAR TO 'N[A-E]?POLEON[R-Z]?'     -- Verdadero

El primer caso es verdadero porque la letra O se encuentra 0 ó 1 vez

El segundo caso es verdadero porque la letra X se encuentra 0 ó 1 vez

El tercer caso es falso porque la letra X se encuentra 2 veces, y debería encontrarse 0 ó 1 vez para ser verdadero

El cuarto caso es verdadero porque la letra L se encuentra una vez en el rango A .. M

El quinto caso es verdadero porque la letra A se encuentra una vez en el rango A .. E y las letras R .. Z se encuentran 0 veces después de POLEON

El * indica que el carácter o clase que le antecede puede ocurrir 0, 1 ó muchas veces

'NAPOLEON' SIMILAR TO 'NAPO*LEON'               -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPOX*LEON'              -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPOXX*LEON'             -- Falso
'NAPOLEON' SIMILAR TO 'NAPO[A-M]*EON'           -- Verdadero
'NAPOLEON' SIMILAR TO 'N[A-E]*POLEON[R-Z]*'     -- Verdadero

El primer caso es verdadero porque la letra O se encuentra 0, 1, ó más veces

El segundo caso es verdadero porque la letra X se encuentra 0, 1, ó más veces

El tercer caso es falso porque la letra X se encuentra 2 veces, y debería encontrarse 0 ó 1 vez para ser verdadero

El cuarto caso es verdadero porque la letra L se encuentra una vez en el rango A .. M

El quinto caso es verdadero porque la letra A se encuentra una vez en el rango A .. E y las letras R .. Z se encuentran 0 veces después de POLEON

El + indica que el carácter o clase que le antecede debe ocurrir 1 vez ó más de 1 vez. O sea que es obligatorio que ocurra.

'NAPOLEON' SIMILAR TO 'NAPO_+'                  -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPO+LEON'               -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPOLEONX+'              -- Falso
'NAPOLEON' SIMILAR TO 'NAPO[A-P]+EON'           -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPO[[:DIGIT:]]+EON'     -- Falso

 El primer caso es verdadero porque el carácter anterior al + puede ser cualquiera, y ocurrió una vez

El segundo caso es verdadero porque la letra O ocurrió una vez

El tercer caso es falso porque la letra X no ocurrió ni una vez

El cuarto caso es verdadero porque la letra L se encuentra en el rango A .. P y ocurrió una vez

El quinto caso es falso porque se necesitaba una letra L pero en su lugar hay dígitos

Si un carácter o una clase son seguidos por un número rodeado por llaves, ese carácter o esa clase deben repetirse exactamente ese número de veces

'NAPOLEON' SIMILAR TO 'NAPO{2}LEON'               -- Falso
'NAPOLEON' SIMILAR TO 'NAPO{1}LEON'               -- Verdadero
'NAPOLEON' SIMILAR TO 'NAP[[:ALPHA:]]{1}LEON'     -- Verdadero
'NAPOLEON' SIMILAR TO 'NAP[[:ALPHA:]]{2}LEON'     -- Falso
'NAPOLEON' SIMILAR TO 'NA[M-R]{2}LEON'            -- Verdadero

El primer caso es falso porque la letra O debería estar 2 veces y está solamente 1 vez

El segundo caso es verdadero porque la letra O está 1 vez

El tercer caso es verdadero porque la letra O está incluida una vez en la clase predefinida [:ALPHA:]

El cuarto caso es falso porque la letra O está incluida una vez en la clase predefinida [:ALPHA:] y se está pidiendo que esté incluida dos veces

El quinto caso es verdadero porque la letra P está incluida una vez en el rango M .. R y la letra O está incluida una vez en ese rango, por lo tanto son 2

Si el número es seguido por una coma, entonces el carácter o la clase que le preceden deben repetirse al menos ese número de veces

'NAPOLEON' SIMILAR TO 'NAPO{1,}LEON'     -- Verdadero
'NAPOLEON' SIMILAR TO 'NAPO{2,}LEON'     -- Falso
'NAPOLEON' SIMILAR TO 'NA[E-S]{3,}'      -- Verdadero
'NAPOLEON' SIMILAR TO 'NA[F-S]{3,}'      -- Falso

El primer caso es verdadero porque la letra O se repite una vez o más

El segundo caso es falso porque la letra O no se repite dos veces o más

El tercer caso es verdadero porque los caracteres del rango E .. S se repiten 3 veces o más

El cuarto caso es falso porque la letra E no está en el rango

Si dentro de las llaves hay dos números separados por comas, siendo el segundo mayor o igual que el primero, entonces el carácter o la clase que le preceden deben repetirse al menos el primer número y como máximo, el segundo número

'NAPOLEON' SIMILAR TO 'NA[E-S]{2,3}'     -- Falso
'NAPOLEON' SIMILAR TO 'NA[E-S]{2,6}'     -- Verdadero
'NAPOLEON' SIMILAR TO 'NA[E-S]{4,6}'     -- Verdadero

El primer caso es falso porque las letras E .. S se repiten 6 veces y deberían repetirse como máximo 3 veces

El segundo caso es verdadero porque las letras E .. S se repiten 6 veces, y deberían repetirse entre 2 y 6 veces

El tercer caso es verdadero porque las letras E .. S se repiten 6 veces y deberían repetirse entre 4 y 6 veces

La disyunción se realiza con el operador | y es verdadera cuando hay coincidencia con alguno de los dos ítems

'NAPOLEON' SIMILAR TO 'NAPO|LEON'              -- Falso
'NAPOLEON' SIMILAR TO 'NAPOLEON|BONAPARTE'     -- Verdadero
'NAPOLEON' SIMILAR TO 'MAR_+|NA_+|TUZ_+'       -- Verdadero

El primer caso es falso porque NAPOLEON no es igual a NAPO y tampoco es igual a LEON

El segundo caso es verdadero porque NAPOLEON es igual a NAPOLEON

El tercer caso es verdadero porque NA_+ es verdadero

Subexpresiones

Puedes utilizar subexpresiones si las rodeas con paréntesis.

'NAPOLEON' SIMILAR TO 'NA(PA|PE|PI|PO|PU)LEON'     -- Verdadero
'NAPOLEON' SIMILAR TO 'NA(P[M-R]+)LEON'            -- Verdadero
'NAPOLEON' SIMILAR TO 'NA([M-R]{2})LEON'           -- Verdadero

El primer caso es verdadero porque PO está incluido en la subexpresión

El segundo caso es verdadero porque PO está incluido en la subexpresión

El tercer caso es verdadero porque PO está incluido en la subexpresión

Caracteres especiales de escape

Para que la comparación pueda ser hecha contra un carácter que se usa dentro de los patrones de caracteres, ese carácter debe ser «escapado».

'NAPOLEON (FRANCIA)' SIMILAR TO 'N[^ ]+ \(F[^ ]+\)' ESCAPE '\'     -- Verdadero

Este caso es verdadero porque se está verificando que empiece con N, que luego haya un espacio en blanco, un paréntesis abierto, una letra F, y un paréntesis cerrado.

Conclusión:

El predicado de comparación SIMILAR TO es el más poderoso de todos los predicados de comparación pero así también es el más difícil de entender y de usar completamente.

Sin embargo, bien que vale la pena el esfuerzo porque si dominas el uso de SIMILAR TO entonces tus comparaciones que involucren a strings serán muy rápidas, muy eficientes, y mucho más cortas de escribir que si debes hallar los mismos resultados sin usar SIMILAR TO.

Artículos relacionados:

Los predicados de comparación

El índice del blog Firebird21

El foro del blog Firebird21

 

Los predicados de comparación

2 comentarios

Una de las principales utilidades del comando SELECT es realizar búsquedas en las tablas, así podremos saber si algún dato existe o no existe en ellas. Cuando necesitamos realizar búsquedas podemos emplear varios predicados de comparación, ellos son:

  • BETWEEN … AND …
  • CONTAINING
  • IN
  • LIKE
  • STARTING WITH
  • SIMILAR TO

BETWEEN … AND …

El predicado de comparación BETWEEN … AND … debe recibir dos argumentos (que sean de tipos compatibles) y devuelve como resultado las filas que contienen esos valores o cualquier valor incluido entre ellos. Ejemplo:

SELECT
   CLI_NOMBRE
FROM
   CLIENTES
WHERE
   CLI_CODIGO BETWEEN 1 AND 100

Este SELECT devolverá los nombres de todos los clientes cuyos códigos se encuentren entre 1 y 100, inclusives.

CONTAINING

El predicado de comparación CONTAINING busca dentro de una columna alfanumérica los caracteres pedidos. Importante: no distingue entre mayúsculas y minúsculas. Ejemplo:

SELECT
   CLI_NOMBRE
FROM
   CLIENTES
WHERE
   CLI_NOMBRE CONTAINING 'MAR'

 Este SELECT devolverá los nombres de todos los clientes que tengan los caracteres ‘MAR’ en la columna CLI_NOMBRE. Así, podríamos tener a: MARÍA TERESA, CLAUDIA MARCELA, ROSEMARY, María Estela, Ana María, Tamara, etc.

IN

El predicado de comparación IN es una forma simplificada de escribir el operador de comparación OR, los resultados obtenidos serán los mismos pero se escribe menos.

SELECT
   CLI_NOMBRE
FROM
   CLIENTES
WHERE
   CLI_CODIGO IN (15, 21, 45)

Este SELECT nos devolverá los nombres de los clientes cuyos códigos sean 15, 21, ó 45. Podríamos haberlo escrito así:

SELECT
   CLI_NOMBRE
FROM
   CLIENTES
WHERE
   CLI_CODIGO = 15 OR
   CLI_CODIGO = 21 OR
   CLI_CODIGO = 45

Como puedes ver, al usar el predicado de comparación IN se escribe mucho menos. Y cuanto mayor sea la cantidad de valores a evaluar mayor será lo que se ahorrará. Por lo tanto, si todos los valores serán comparados contra una misma columna (como en los dos ejemplos anteriores) entonces lo más conveniente siempre es usar el predicado IN y no el operador OR. Imagínate todo lo que te ahorrarás de escribir si los valores a comparar no fueran 3 como en estos ejemplos sino 20.

LIKE

El predicado de comparación LIKE tiene cuatro características muy importantes:

  1. Distingue entre mayúsculas y minúsculas
  2. Utiliza patrones de caracteres
  3. Solamente usa índices cuando no empieza con un comodín
  4. Si alguno de los valores es NULL, entonces devuelve NULL

Dentro de los patrones de caracteres podemos usar «comodines». Ellos son:

‘%’      equivale a cualquier string, de cualquier longitud

‘_’      equivale a un solo caracter

También puede usar un caracter de escape. ¿Qué es un caracter de escape? Un caracter que le dice al Firebird que busque también al caracter que se encuentra a continuación, y lo usaríamos cuando en nuestra búsqueda queremos hallar también a los comodines % y _. Si no existiera el caracter de escape entonces no habría forma de buscar a esos dos caracteres. Ejemplos:

CLI_NOMBRE LIKE 'MAR%'                  -- Devolverá los nombres de todos los clientes que empiecen con MAR
CLI_NOMBRE LIKE 'JULI_'                 -- Devolverá los nombres de todos los clientes que tengan 5 caracteres y los cuatro primeros sean JULI
CLI_NOMBRE LIKE '%MAR%'                 -- Devolverá los nombres de todos los clientes que tengan MAR dentro suyo
PRD_NOMBRE LIKE '%A\_B%' ESCAPE '\'     -- Devolverá todos los nombres de productos que contienen A_B
PRD_NOMBRE LIKE '%\_%' ESCAPE '\'       -- Devolverá todos los nombres de productos que tienen un guión bajo

En el primer y en el segundo casos se usará un índice (si hay uno disponible, por supuesto) porque el patrón de caracteres no empieza con un comodín. En los restantes casos, nunca se usará un índice, porque empiezan con un comodín.

STARTING WITH

El predicado de comparación STARTING WITH es muy parecido a LIKE y se lo puede utilizar cuando se conocen los primeros caracteres del string buscado.

SELECT
   CLI_NOMBRE
FROM
   CLIENTES
WHERE
   CLI_NOMBRE STARTING WITH 'MAR'

Este SELECT nos traerá a MARIA TERESA, MARTHA, MARCELA, MARLENE, etc. O sea, todos los nombres que empiezan con MAR.

SIMILAR TO

El predicado de comparación SIMILAR TO también es parecido a LIKE pero nos permite escribir condiciones más complejas.

SELECT
   CLI_NOMBRE
FROM
   CLIENTES
WHERE
   CLI_NOMBRE SIMILAR TO '[CS]%'

Este SELECT nos devolverá los nombres de todos los clientes que empiecen con C o con S.

SELECT
   CLI_NOMBRE
FROM
   CLIENTES
WHERE
   CLI_NOMBRE SIMILAR TO '[CS]%a'

Este SELECT nos devolverá los nombres de todos los clientes que empiecen con C o con S y que terminen con a.

Artículos relacionados:

Los predicados existenciales

El índice del blog Firebird21

 

Libro electrónico para consultas con Firebird

17 comentarios

Parece que la idea de tener un libro en castellano que trate sobre consultas Firebird tiene bastante apoyo. Ahora, se me ocurrió otra idea, relacionada con la anterior.

Los libros en papel van camino a extinguirse, al menos los relacionados con temas informáticos, la mayoría los lee o los leerá dentro de pocos años en computadoras, tabletas, celulares, etc.

Así que no tiene mucho sentido escribir un libro de esa forma en una materia tan técnica. Aunque el libro esté en PDF seguirá siendo material imprimible y estático.

Mi nueva idea es crear un programa de computadora que sea un libro. En otras palabras, lo leerás como si se tratara de un libro pero haciendo clic sobre un botón podrás ver los ejemplos ejecutarse e inclusive podrás cambiar esos ejemplos.

¿Existe ya algo así?

Probablemente, aunque yo aún no lo he visto. Si lo conoces te agradecería que me enviaras el enlace.

Y si no existe, mejor, estaría creando algo nuevo. Una nueva técnica de enseñanza.

Entonces, irías leyendo el libro y cuando quieras podrás cambiar los ejemplos mostrados. De esa forma aprenderás más rápido.

Así, si en una «página» ves algo como:

SELECT
   *
FROM
   CLIENTES
WHERE
   CLI_NOMBRE STARTING WITH 'A'

podrás cambiarlo a algo como:

SELECT
   *
FROM
   CLIENTES
WHERE
   CLI_NOMBRE STARTING WITH 'B'

Y al instante verás los resultados. Me parece que será muy útil, al menos a mí me hubiera gustado que existiera algo así cuando empecé a aprender el lenguaje SQL.

Entonces, la aplicación constaría de:

  • Un programa ejecutable. El «libro» en sí
  • Muchas «páginas». Cada «página» mostraría un SELECT, explicaría como funciona, mostraría los resultados, y permitiría cambiar el SELECT
  • Una Base de Datos embebida

Características:

  • El programa sería portable, para poder llevarlo en un pen-drive y ejecutarlo en cualquier computadora.
  • Se usará la arquitectura embedded del Firebird para no necesitar instalar al Firebird
  • El «libro» tendrá muchas «páginas». Cada «página» contendrá la explicación de un SELECT. Ese SELECT el lector podrá modificarlo.
  • Haciendo clic sobre un botón el lector podrá ver en la pantalla el resultado de la ejecución del SELECT
  • También podrá ver el PLAN que se usó en ese SELECT
  • Y también podrá ver un análisis del rendimiento de ese SELECT
  • El «libro» estará organizado por categorías o capítulos, cada uno tratando un tema específico
  • Si el lector quiere buscar una «página» o un SELECT, tendrá varias maneras de hacerlo
  • Cada «página» podrá ser impresa, si el lector así lo desea.
  • Se podrá imprimir el «libro» completo, con la configuración original o con la configuración cambiada por el lector

Bien, esa es mi nueva idea. La cual además tiene otra ventaja sobre la de escribir un libro en PDF, y es que podría permitir descargar el «libro» y algunas de sus «páginas» (digamos unas 15) para que el lector pueda evaluar la aplicación. Y luego, si le interesa, podrá descargar las restantes 200 páginas (o algo así) después de pagar algunos dólares por ellas.

¿Tienes comentarios? Quisiera conocerlos

Walter.

 

Libros sobre Firebird en castellano

24 comentarios

Tengo entendido que este blog es la principal fuente de información sobre Firebird en idioma castellano (y no lo digo para auto-alabarme o algo así).

Hay libros sobre Firebird en otros idiomas (inglés, portugués, ruso, etc.) pero creo que no hay en castellano, ni siquiera traducidos.

En estos últimos meses varios lectores del blog me han pedido que escriba un libro sobre Firebird. La idea me parece interesante aunque me tomará mucho tiempo realizarla, mi tiempo libre es muy escaso.

Inicialmente pensé en escribir un libro con una selección de los artículos del blog, mejorados por supuesto, para que sean más útiles. Pero el tema es tan amplio que quizás ni en un año terminaría el libro.

Y luego se me ocurrió que había una alternativa, empezar con algo más modesto al principio.

Algo que muchos necesitamos es escribir SELECTs para realizar consultas a las bases de datos, y cuanto más rápido escribamos esos SELECTs y cuanto más rápido se ejecuten, mejor. Entonces, mi idea actual es escribir un libro que trate sólo y exclusivamente sobre SELECTs.

El título tentativo del libro (que por supuesto podría cambiarlo más adelante) es: «Consultas con Firebird. De principiante a experto»

A grosso modo creo que tendrá unos 200 a 250 SELECTs, todos explicados claramente para que sea fácil entender lo que hacen y como lo hacen. Habrá una Base de Datos que podrán descargar desde Internet para poder ejecutar esos ejemplos y comprobar los resultados.

Aún no sé como comercializaré el libro. Desde luego que todos preferirían que sea gratis (y quizás lo sea) pero estoy pensando en que podría cobrar algunos dólares por la descarga, porque escribirlo me llevará mucho tiempo.

Si funciona, y tiene éxito, entonces escribiré otro libro más completo y más detallado.

En fin, esa es la idea, ¿qué opinan? Espero sus comentarios.

Walter.

 

Usando el comando NET del Windows

Deja un comentario

Este artículo no está directamente relacionado con Firebird pero puede serte útil alguna vez.

Windows tiene un comando llamado NET (red, en castellano) que se usa para realizar diversas tareas en la red. Pues ver las opciones disponibles simplemente escribiendo NET y presionando la tecla Enter.

NET1

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

Si quieres ayuda sobre alguna de esas opciones entonces escribes NET, el nombre de la opción, y el símbolo de interrogación, así:

NET2

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

Es conveniente que leas sobre cada una de las opciones del comando NET porque pueden serte muy útiles. Aquí y ahora veremos la opción USE.

NET3

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

¿Para qué sirve NET USE? Para compartir recursos.

Ejemplo 1. Mapear una carpeta

NET4

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

La carpeta NEGOCIOS es una carpeta compartida de la computadora que tiene el IP 192.168.0.4

A partir de este momento, aunque la carpeta NEGOCIOS se encuentre en otra computadora podemos escribir algo como DIR Z: para ver el contenido de esa carpeta.

NET5

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

 Y con el Explorador del Windows veremos algo como esto:

NET6

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

Donde, como puedes ver, te muestra que la unidad Z: está mapeada a la carpeta NEGOCIOS de la computadora que tiene el IP 192.168.0.4

Eso implica que a partir de este momento en lugar de escribir \\192.168.0.4\NEGOCIOS puedes escribir simplemente Z:, y será lo mismo.

Desde luego que no solamente puedes usar la letra Z, puedes usar cualquier letra que no esté en uso, entre la D y la Z inclusives. En mi caso como C, D, y E ya están usadas entonces las letras que puedo usar van desde la F hasta la Z.

Ejemplo 2. Desmapear una carpeta

Ok, supongamos que ya no quieres que la carpeta NEGOCIOS continúe mapeada ¿qué haces?

NET7

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

Y a partir de este momento ya no podrás acceder a la carpeta NEGOCIOS mediante la unidad Z:

Artículo relacionado:

El índice del blog Firebird21

Guardando las columnas BLOB en tablas separadas

5 comentarios

Las columnas de tipo BLOB nos permiten guardar en ellas contenido muy grande: típicamente mayor a 32765 bytes. ¿Por qué ese número? Porque una columna de tipo VARCHAR puede guardar hasta 32765 bytes entonces usaríamos una columna BLOB cuando necesitamos un espacio de almacenamiento mayor.

El contenido de las columnas de tipo VARCHAR puede ser indexado, el contenido de las columnas de tipo BLOB no puede ser indexado.

Entonces, si estamos seguros de que la columna siempre contendrá menos de 32765 bytes lo conveniente es que sea de tipo VARCHAR, para poder indexarla cuando lo deseemos.

Ok, ya estudiamos el caso y decidimos que necesitamos una columna de tipo BLOB, ¿en cuál tabla la guardamos?

Aquí tenemos dos posibilidades:

  1. Guardarla en la misma tabla que los demás datos
  2. Guardarla en una tabla separada

Por ejemplo, tenemos una tabla de EMPLEADOS y queremos guardar las fotografías de los empleados. O tenemos una tabla de VENTAS y queremos guardar las facturas escaneadas.

Si elegimos la posibilidad 1. entonces debemos olvidarnos por completo de escribir algo como SELECT * FROM EMPLEADOS, o como SELECT * FROM VENTAS, porque el tiempo que se demorará en obtener el conjunto resultado puede ser muy grande.

Por el contrario, si elegimos la posibilidad 2. nunca tendremos ese problema ya que la columna BLOB se encuentra en otra tabla y para leer su contenido debemos específicamente escribir un JOIN.

Elegir la posibilidad 1. tiene las ventajas de que nuestros SELECTs son más sencillos de escribir y que usamos menos espacio en el disco duro pero tiene la desventaja de que los resultados se obtienen más lentamente. Y nuestra prioridad siempre debe estar enfocada en conseguir la máxima velocidad posible.

Ejemplo. Guardar las fotografías de los empleados

BLOB1

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

En la Captura 1. vemos la estructura de la tabla EMPLEADOS.

BLOB2

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

Y en la Captura 2. vemos la estructura de la tabla EMPLEADOSFOT, que es la tabla donde guardamos las fotografías de los empleados.

Evidentemente al usar dos tablas estamos ocupando más espacio en el disco duro porque tenemos dos columnas (EMF_IDENTI y EMF_IDECAB en nuestro ejemplo) que no necesitaríamos si usáramos una sola tabla.

Al usar dos tablas cuando consultamos a la tabla de EMPLEADOS no recuperamos las fotografías, para recuperarlas debemos escribir un JOIN similar a éste:

SELECT
   E.EMP_IDENTI,
   E.EMP_NOMBRE,
   E.EMP_APELLD,
   F.EMF_FOTOGR
FROM
   EMPLEADOS    E
LEFT JOIN
   EMPLEADOSFOT F
      ON E.EMP_IDENTI = F.EMF_IDECAB

Empleamos la cláusula LEFT JOIN para poder obtener los datos de todos los empleados, inclusive los de aquellos que no tienen fotografías.

Conclusión:

Si necesitas tener columnas de tipo BLOB entonces lo conveniente y lo recomendable es guardarlas en tablas separadas. Relacionarás la tabla padre y la tabla hija con un identificador común para escribir la cláusula JOIN cuando necesites recuperar la columna de tipo BLOB, tal como se vio en el SELECT de arriba.

Si guardas los datos y la columna de tipo BLOB en una sola tabla entonces tus SELECTs serán más sencillos de escribir y ahorrarás espacio en el disco duro. Pero tus consultas serán más lentas.

Y tu prioridad debería ser tener consultas rápidas, ya que en esta época usar unos pocos bytes más no importa.

Artículos relacionados:

Usando columnas de tipo BLOB

El índice del blog Firebird21

 

Older Entries