El tipo de datos BOOLEAN en Firebird 3

6 comentarios

Un tipo de datos que siempre le faltó a Firebird fue el BOOLEAN… hasta ahora.

Desde siempre contábamos con los tipos de datos: SMALLINT, INTEGER, BIGINT, FLOAT, DOUBLE PRECISION, NUMERIC, DECIMAL, DATE, TIME, TIMESTAMP, CHAR, VARCHAR, BLOB, pero no contábamos con BOOLEAN, entonces si lo necesitábamos (algo muy frecuente al crear una tabla) lo simulábamos creando un dominio como el siguiente:

CREATE DOMAIN D_BOOLEAN AS
   CHAR(1)
      CHECK (VALUE = 'F' OR VALUE = 'T');

Funcionaba bien, claro que sí, pero no es realmente un tipo de datos BOOLEAN.

¿Por qué no?

Porque le faltan los predicados lógicos. Es decir las comparaciones por verdadero o falso.

Un ejemplo de lo que ahora podemos hacer:

UPDATE
   MiTabla
SET
   MiColumnaBoolean = (MiValor1 IS DISTINCT FROM MiValor2)

Una comparación puede darnos uno de estos tres resultados posibles:

  • Verdadero
  • Falso
  • Nulo o desconocido

(Recuerda que en SQL un valor nulo significa: “desconocido”)

En el ejemplo anterior, si MiValor1 es distinto de MiValor2 en MiColumnaBoolean se guardará Verdadero, ya que la condición se cumplió. Si alguno de esos valores era nulo entonces se guardará Desconocido (porque el resultado de comparar un valor desconocido con cualquier otro valor siempre es desconocido).

Valores posibles de una columna de tipo BOOLEAN

Si declaramos que una columna será de tipo BOOLEAN, en ella podremos guardar cualquiera de los siguientes valores:

  • TRUE
  • FALSE
  • UNKNOWN

TRUE significa “verdadero”, FALSE significa “falso” y UNKNOWN significa “desconocido”.

IMPORTANTE: podemos usar NULL o UNKNOWN, como nos guste más, ambas palabras son sinónimos y pueden usarse intercambiablemente, así que usar una u otra depende del gusto de cada quien.

El operador IS

Para hacer las comparaciones podemos usar el operador IS [NOT], escribiendo algo como:

MiColumna1 IS TRUE
MiColumna2 IS FALSE
MiColumna3 IS NOT TRUE
MiColumna4 IS NOT FALSE
MiColumna5 IS UNKNOWN
MiColumna6 IS NULL
MiColumna7 IS DISTINCT FROM MiColumna1

Los operadores de comparación

Además del operador IS que vimos en el apartado anterior, también podemos comparar con: “=”, “<“, “<=”, “>”, “>=”, “!=”, “<>”

Comparación abreviada

Cuando comparamos por “verdadero”, podemos escribir algo como:

WHERE
   MiColumnaBoolean1

Fíjate que no escribimos MiColumnaBoolean1 IS TRUE, ya que el IS TRUE está implícito. Podemos escribirlo, si queremos, pero no es necesario. Similarmente, para comparar con “falso” podríamos escribir:

WHERE
   NOT MiColumnaBoolean1

En este caso, la condición se cumplirá cuando el valor guardado en la columna MiColumnaBoolean1 sea “falso”. También podríamos haber escrito: MiColumnaBoolean1 IS FALSE, al igual que antes, es cuestión de gustos usar una forma u otra.

Valores devueltos por el comando SELECT

Cuando en la lista de columnas que muestra nuestro SELECT existe alguna de tipo BOOLEAN, los valores que podemos ver son los siguientes:

<true>

<false>

<null>

Convirtiendo un tipo de datos BOOLEAN a CHAR o VARCHAR

Solamente podemos convertir el valor de una columna de tipo BOOLEAN a CHAR o a VARCHAR, no se puede convertir a alguno de los demás tipos de datos.

Para ello, usamos la función CAST()

Ejemplo 1. Creando una tabla que tendrá una columna de tipo BOOLEAN

CREATE TABLE
   MiTabla (
      MiColumnaEntera   INTEGER,
      MiColumnaBoolean1 BOOLEAN
) ;

COMMIT;

Ejemplo 2. Insertando valores en una columna de tipo BOOLEAN

INSERT INTO
   MiTabla
      VALUES (1, TRUE);

INSERT INTO
   MiTabla
      VALUES (2, 5 IS DISTINCT FROM 4);

INSERT INTO
   MiTabla
      VALUES (3, NULL);

Ejemplo 3. Asignando valores a una columna de tipo BOOLEAN

UPDATE
   MiTabla
SET
   MiColumnaBoolean1=TRUE,
   MiColumnaBoolean2=FALSE,
   MiColumnaBoolean3=2=4,
   MiColumnaBoolean4=NULL,
   MiColumnaBoolean5=UNKNOWN,
   MiColumnaBoolean6=5 > 1
WHERE
   MiCondición

Ejemplo 4. Consultando los valores de una columna de tipo BOOLEAN

SELECT
   *
FROM
   MiTabla
WHERE
   MiColumnaBoolean1

Esta consulta nos mostrará todas las filas que tengan TRUE en la columna MiColumnaBoolean1.

Ejemplo 5. Consultando por FALSE

SELECT
   *
FROM
   MiTabla
WHERE
   MiColumnaBoolean1 IS FALSE

Ejemplo 6. Consultando por UNKNOWN

SELECT
   *
FROM
   MiTabla
WHERE
   MiColumnaBoolean1 IS UNKNOWN

SELECT
   *
FROM
   MiTabla
WHERE
   MiColumnaBoolean1 IS NULL

Conclusión:

Algo que siempre le había faltado a Firebird era tener un verdadero tipo de datos BOOLEAN, podíamos suplir esa carencia creando un dominio pero hacerlo así no era del todo completo. Ahora, con Firebird 3 sí ya tenemos un verdadero tipo de datos BOOLEAN.

Los resultados de una comparación pueden ser: “verdadero”, “falso”, “desconocido”. Y esos son justamente los valores que podemos guardar en una columna definida como de tipo BOOLEAN, aunque desde luego que usaremos las palabras reservadas: TRUE, FALSE, UNKNOWN.

Para las comparaciones podemos usar el operador IS [NOT] o los operadores de comparación matemáticos.

El resultado de un SELECT que contenga columnas de tipo BOOLEAN puede ser: <true>, <false>, <null>

Recuerda que NULL en SQL significa “desconocido”, y la palabra UNKNOWN también significa “desconocido”, por eso pueden usarse como sinónimos.

Artículos relacionados:

¿Por qué Firebird 3?

Los archivos de configuración del Firebird 3

Entendiendo a los plug-in del Firebird 3

Parametrizando el archivo DATABASES.CONF

Agregando el usuario SYSDBA en Firebird 3

El índice del blog Firebird21

El foro del blog Firebird21

Anuncios

Valores de las variables en un stored procedure

Deja un comentario

Leyendo un comentario que escribió Jaume en el artículo:

Entendiendo a los Stored Procedures

pensé que alguien más podría tener esa misma confusión y por tal motivo estoy escribiendo este artículo. Es para aclarar el comportamiento de las variables en un stored procedure.

Veamos un ejemplo de un stored procedure muy sencillo, sólo para mostrar donde puede existir confusión:

CREATE PROCEDURE VALOR_ANTERIOR
   RETURNS(
      tcNombre TYPE OF COLUMN CLIENTES.CLI_NOMBRE)
AS
BEGIN

   tcNombre = 'PRUEBA';

   FOR SELECT
      CLI_NOMBRE
   FROM
      CLIENTES
   WHERE
      CLI_IDENTI > 1000000
   INTO
      :tcNombre
   DO
      SUSPEND;

   IF (tcNombre = 'PRUEBA') THEN
      SUSPEND;

END;

Evidentemente este es un stored procedure seleccionable (sabemos eso porque tiene el comando SUSPEND dentro de él). Y la pregunta es: ¿qué valor o valores devolverá este stored procedure cuando lo ejecutemos con el comando SELECT?

SELECT
   *
FROM
   VALOR_ANTERIOR

Bien, eso depende de si hay alguna fila que tenga en la columna CLI_IDENTI un valor mayor que 1000000 ó no. Si hay una o más filas, entonces devolverá el nombre de los respectivos clientes pero si ninguna fila cumple con esa condición entonces devolverá la palabra ‘PRUEBA’.

¿Por qué devuelve ‘PRUEBA’ y no devuelve NULL?

Uno podría pensar que si ninguna fila cumple con la condición entonces debería devolver NULL, sin embargo no es así, devuelve el valor que anteriormente tenía la variable tcNombre, en este caso ‘PRUEBA’. ¿Por qué eso?

Eso es porque un SELECT puede devolver cero filas, eso ocurre cuando la tabla no tiene filas o cuando ninguna fila cumple con la condición. En ese caso el valor que tenían las variables asignadas por el SELECT (las que se encuentran después de la cláusula INTO) no puede cambiar ya que ninguna fila fue retornada. La asignación a esas variables se hace solamente después de obtener una fila, como es lógico.

En consecuencia, si ninguna fila es retornada todas esas variables mantienen el valor que tenían anteriormente, porque ninguna asignación fue hecha a ellas. En este caso tcNombre seguirá valiendo ‘PRUEBA’.

Para que tcNombre valiera NULL, el Firebird tendría que asignarle NULL antes de ejecutar al SELECT pero ¿para qué haría eso? sería una causa de conflicto porque una columna de un SELECT puede legítimamente valer NULL y en ese caso ¿cómo se diferenciaría entre un NULL previamente asignado y un NULL como valor legítimo de una columna? No habría forma de diferenciar a un NULL del otro, por lo tanto el Firebird hace lo más lógico, coherente, y racional: asignarle valores a las variables solamente después de obtener una fila del SELECT, nunca antes.

Así que, el Firebird hace lo correcto.

Por lo tanto, si un SELECT no retorna filas, todas las variables que se encuentren después de la claúsula INTO mantendrán el valor que tenían antes del SELECT.

No asumas que el valor de dichas variables será NULL, porque podría no ser así.

Artículos relacionados:

Entendiendo a los Stored Procedures

El índice del blog Firebird21

El foro del blog Firebird21

 

Uso de la Primary Key

2 comentarios

Como sabes, una Primary Key es una clave que tiene estas dos características:

  • Sus valores son únicos (jamás se pueden repetir)
  • Sus valores no pueden ser NULL

Eso nos permite identificar a cada fila (a cada registro) de una forma unívoca, es decir con la total seguridad de que estamos identificando a la fila correcta.

¿Cuándo deberíamos utilizar una Primary Key?

1. Cuando deseamos consultar la tabla en el mismo orden en que las filas fueron cargadas

Por ejemplo, escribiendo:

SELECT
   PRD_IDENTI,     -- Identificador y Primary Key
   PRD_NOMBRE,     -- Nombre del producto
   PRD_PRECTO      -- Precio de costo del producto
FROM
   PRODUCTOS
ORDER BY
   PRD_IDENTI

2. Cuando hacemos búsquedas y queremos asegurarnos de obtener las filas correctas, sin la posibilidad de equivocarnos. Por ejemplo, supongamos que queremos borrar de la tabla de PRODUCTOS al producto cuyo identificador es 12345 y cuyo nombre es ‘Jugo de naranjas’. Si escribimos:

DELETE FROM
   PRODUCTOS
WHERE
   PRD_IDENTI = 12345

estaremos seguros de borrar a un solo producto, el que tiene como identificador al número 12345.

En cambio, si escribimos algo como:

DELETE FROM
   PRODUCTOS
WHERE
   PRD_PROCED = 'Japón'

Estaríamos borrando a todos los productos cuyo país de procedencia es Japón. Podríamos creer que hay un solo producto que tiene esa procedencia pero en realidad hay varios y en ese caso los estaríamos borrando a todos, lo cual sería un error grave.

Por ese motivo, en general, cuando de borrar se trata deberíamos usar la Primary Key, es más seguro.

DECODE()

1 comentario

Descripción: Es una forma abreviada de escribir el “CASE simple” en el cual una expresión es comparada con varias otras expresiones hasta que una coincidencia es encontrada. El resultado es determinado por el valor que se encuentra después de la expresión coincidente. Si no se encuentra una coincidencia el valor por defecto es devuelto. Y si no hay valor por defecto, NULL es devuelto.

Tipo de resultado: Varía

Sintaxis:

DECODE(<expresión_a_examinar>,

<expresión>, resultado

[, <expresión>, resultado …]

[, <resultado_por_defecto>])

El CASE simple equivalente sería:

CASE <expresión_a_examinar>

WHEN <expresión> THEN resultado

[WHEN <expresión> THEN resultado …]

[ELSE resultado_por_defecto]

END

Aviso: Las coincidencias son hechas con el operador “=”, así que si la expresión_a_examinar es NULL, no habrá coincidencia con ninguna <expresión>, ni siquiera con aquellas que sean NULL

Ejemplo:

SELECT
   MVC_FECHAX,
   DECODE(MVC_TIPMOV,
      'ECM', 'Compra',
      'SVT', 'Venta',
      'COB', 'Cobranza',
      'PAG', 'Pago',
      'Desconocido'),
   DECODE(MVC_TIPDOC,
      'FCO', 'Factura contado',
      'FCR', 'Factura crédito',
      'Desconocido'),
   MVC_NRODOC,
   DECODE(MVC_MONEDA,
      0, 'Moneda local',
      1, 'Dólares',
      2, 'Euros',
      3, 'Libras esterlinas',
      4, 'Yenes')
FROM
   MOVIMCAB

Tratamiento de columnas NULL

8 comentarios

En el lenguaje SQL la palabra NULL significa “desconocida/o”.

Cuando establecemos que una columna de una tabla puede ser de tipo NULL lo que le estamos diciendo al Firebird es: “en esta columna podríamos tener algunos valores desconocidos”.

¿Establecer una columna como NULL podría causarnos problemas?

Pues sí, si es que nuestra aplicación no maneja correctamente a los NULL.

Entender como tratar a las columnas que pueden contener NULL es fundamental para que nuestra aplicación sea exitosa. En general, como una regla mientras no domines SQL:

  • No debes establecer NULL para las columnas numéricas, en lugar de eso ponerles como valor por defecto el número 0 (cero)
  • No debes establecer NULL para las columnas alfanuméricas, en lugar de eso ponerles como valor por defecto un espacio en blanco
  • No debes establecer NULL para las columnas de tipo fecha, en lugar de eso ponerles como valor por defecto una fecha muy antigua y que nunca usarás, como: ’01/01/1900′

¿Por qué es preferible ponerles valores por defecto a las columnas?

Porque el tratamiento de los valores de esas columnas se facilita. Por ejemplo, la función agrupada AVG() ignora a las columnas que tienen NULL para hallar el promedio. Y en general vas a querer que no las ignore. Además, en tu aplicación podrías olvidarte que una columna puede contener NULL y perderías tiempo buscando el problema, problema que nunca habrías tenido si la columna tuviera un valor por defecto.

Dependiendo de nuestro lenguaje de programación podemos hacer asignaciones NULL y también hacer comparaciones para saber si una columna es NULL o no lo es.

En el lenguaje Visual FoxPro escribiríamos:

lcNombre = NULL          && le asigna NULL a la variable lcNombre
IF ISNULL(lcNombre)      && si la variable lcNombre es NULL, entonces ...
IF !ISNULL(lcNombre)     && si la variable lcNombre no es NULL, entonces...

Tener columnas de tipo NULL es muy útil siempre y cuando sepas como usarlas. Si ese no es el caso, es mejor evitarlas y ponerles valores por defecto a todas las columnas, vivirás más tranquilo así.

Si tienes columnas NULL, en tu aplicación a cada momento podrías tener que verificar si una columna es NULL o no lo es. Y eso te hará perder tiempo y complicará tu código fuente.

Una forma de evitar ese problema es que en tu Base de Datos uses la función COALESCE(), como se muestra a continuación:

SELECT
   COALESCE(MiColumnaNumérica, 0) AS MiNumero
FROM
   MiTabla

La función COALESCE() devuelve el primer argumento que no sea NULL. O sea que en este ejemplo si MiColumnaNumérica no es NULL, ese es el valor que devolverá. Y si MiColumnaNumérica es NULL devolverá 0.

.