PostgreSQL

Sintaxe no PostgreSQL: booleanos

Se você não leu, recomendo que veja o artigo sobre estrutura léxica do PostgreSQL antes de começar por aqui.

Toda informática é baseada na lógica booleana. Os dados booleanos são utilizados geralmente como flags para marcar se determinado atributo de uma tabela está presente ou não. Por incrível que pareça, apesar de ser o tipo de dado mais simples que existe e fazer parte do padrão SQL, poucos SGDBs implementam este tipo de dado e armazenem ele como uma coluna de uma tabela.

Uma coluna booleana pode ter apenas 3 valores distintos, falso, verdadeiro ou nulo. O PostgreSQL possui 3 palavras reservadas para lidar com estes valores: FALSE, TRUE e NULL. Veja como exemplo a tabela verdade envolvendo valores booleanos:

teste=# CREATE TABLE bol (x boolean, y boolean);
CREATE TABLE
                         ^
teste=# INSERT INTO bol VALUES (FALSE, FALSE);
INSERT 0 1
teste=# INSERT INTO bol VALUES (FALSE, TRUE);
INSERT 0 1
teste=# INSERT INTO bol VALUES (FALSE, NULL);
INSERT 0 1
teste=# INSERT INTO bol VALUES (TRUE, FALSE);
INSERT 0 1
teste=# INSERT INTO bol VALUES (TRUE, TRUE);
INSERT 0 1
teste=# INSERT INTO bol VALUES (TRUE, NULL);
INSERT 0 1
teste=# INSERT INTO bol VALUES (NULL, NULL);
INSERT 0 1

teste=# SELECT x, y, NOT x AS n_x, NOT y AS n_y, x OR y AS x_or_y, x AND y AS x_and_y FROM bol;
 x | y | n_x | n_y | x_or_y | x_and_y
---+---+-----+-----+--------+---------
 f | f | t   | t   | f      | f
 f | t | t   | f   | t      | f
 f |   | t   |     |        | f
 t | f | f   | t   | t      | f
 t | t | f   | f   | t      | t
 t |   | f   |     | t      |
   |   |     |     |        |
(7 rows)

Note que como resultado o PostgreSQL exibe uma representação dos valores de TRUE, FALSE e NULL como ‘f’, ‘t’ e vazio respectivamente, mas isso é apenas uma representação. Existem outras formas representar verdadeiro e falso:

teste=# INSERT INTO bol VALUES ('t', 'f');
INSERT 0 1

teste=# INSERT INTO bol VALUES ('T', 'F');
INSERT 0 1

teste=# INSERT INTO bol VALUES ('true', 'false');
INSERT 0 1

teste=# INSERT INTO bol VALUES ('TRUE', 'FALSE');
INSERT 0 1

teste=# INSERT INTO bol VALUES ('YES', 'NO');
INSERT 0 1

teste=# INSERT INTO bol VALUES ('yes', 'no');
INSERT 0 1

teste=# INSERT INTO bol VALUES ('y', 'n');
INSERT 0 1

teste=# INSERT INTO bol VALUES ('Y', 'N');
INSERT 0 1

teste=# INSERT INTO bol VALUES ('on', 'off');
INSERT 0 1

teste=# INSERT INTO bol VALUES ('ON', 'OFF');
INSERT 0 1

teste=# INSERT INTO bol VALUES ('0', '1');
INSERT 0 1

teste=# INSERT INTO bol VALUES (0, 1);
ERROR:  column "x" is of type boolean but expression is of type integer
LINE 1: INSERT INTO bol VALUES (0, 1);
HINT:  You will need to rewrite or cast the expression.

teste=# INSERT INTO bol VALUES ('NULL', 'null');
ERROR:  invalid input syntax for type boolean: "NULL"
LINE 1: INSERT INTO bol VALUES ('NULL', 'null');

teste=# INSERT INTO bol VALUES ('', '');
ERROR:  invalid input syntax for type boolean: ""
LINE 1: INSERT INTO bol VALUES ('', '');

Você não pode exagerar muito, se você colocar qualquer número sem aspas simples, o PostgreSQL sempre vai interpretar isso como um número. E NULL só pode ser representado com a sua palavra reservada. Não pode estar entre aspas simples e vazio é diferente de nulo. Você não pode inserir ” como sinônimo para NULL.

O PostgreSQL aceita coisas como ‘t’, ‘true’, ‘y’, ‘yes’, ‘on’, ‘1’ como sinônimos para TRUE. Mas isso funciona dentro de um INSERT. Neste contexto o PostgreSQL faz uma coerção implícita para o tipo correto de dados. Vejamos o que ocorre em outros contextos:

teste=# SELECT TRUE AND FALSE;
 ?column?
----------
 f
(1 row)

teste=# SELECT 'TRUE' AND 'FALSE';
 ?column?
----------
 f
(1 row)

teste=# SELECT 't' AND 'f';
 ?column?
----------
 f
(1 row)

teste=# UPDATE bol SET x = 'on' WHERE y = 'off';
UPDATE 12

So far, so good. Mas existe um contexto que é mais sensível: a chamada de uma função. Suponhamos que você tenha uma função como esta:

CREATE FUNCTION not_and (boolean, boolean) RETURNS boolean AS
$func$
BEGIN
    RETURN NOT ($1 AND $2);
END;
$func$
LANGUAGE plpgsql;

Agora vamos chamar esta função:

teste=# SELECT not_and('true', 'false');
 not_and
---------
 t
(1 row)

Perfeito. Tudo tranquilo. Parece que o PostgreSQL está se comportando exatamente como você esperava. Então onde está o problema? Bom, acontece que no PostgreSQL você pode ter sobrecarga de funções. Ou seja, varias funções com o mesmo nome, recebendo parâmetros diferentes.  Vejamos um exemplo:

CREATE FUNCTION not_and (text, text) RETURNS text AS
$func$
BEGIN
    RETURN $1 || $2;
END;
$func$
LANGUAGE plpgsql;

A função tem o mesmo nome, mas recebe parâmetros do tipo TEXT. Vejamos o que acontece quando fazemos exatamente a mesma chamada que antes:

teste=# SELECT not_and('true', 'false');
  not_and
-----------
 truefalse
(1 row)

Aqui o comportamento foi diferente. E veja que utilizamos exatamente a mesma chamada que antes! O PostgreSQL utilizou a função com parâmetros do tipo TEXT e não do tipo BOOLEAN. Para corrigir isso você tem duas opções:

teste=# SELECT not_and(boolean 'true', boolean  'false');
 not_and
---------
 t
(1 row)

teste=# SELECT not_and(TRUE, FALSE);
 not_and
---------
 t
(1 row)

OU seja: ou você utiliza uma coerção explícita (que você vai utilizar muito com outros tipos de dados em chamadas de funções) ou utiliza as palavras reservadas. Particularmente eu acho que utilizar sempre as palavras reservadas TRUE e FALSE muito mais elegante e evita possíveis dores de cabeça no futuro. Você pode perder um bom tempo até descobrir que existe outra função com o mesmo nome mas parâmetros diferentes…

Existe ainda um contexto peculiar em que valores booleanos podem ser utilizados de forma peculiar. Quando você quer fazer um teste do tipo verdadeiro ou falso, como num IF ou num WHERE, você pode utilizar uma sintaxe mais direta. Veja o UPDATE abaixo com o WHERE escrito de duas formas diferentes:

teste=# UPDATE bol SET x = 'on' WHERE y = TRUE;
UPDATE 3
teste=# UPDATE bol SET x = 'on' WHERE y;
UPDATE 3

Observação importante

Por último um pequeno comentário sobre o uso de booleanos como tipo de dados em suas tabelas. O PostgreSQL aloca um byte e não um bit para armazenar uma coluna do tipo booleano. Em muitos casos você pode utilizar outros tipos de dados no lugar do BOOLEAN, como um TIMESTAMP (que ocupa 8 bytes) por exemplo. Diferente de tempos passados, economizar demais em tipos de dados não é uma grande vantagem. Mas imagine o caso em que você quer um flag para saber se um cliente está inativo ou não. Se você utilizar um TIMESTAMP, você pode deixar a coluna como NULL se o cliente não estiver inativo e inserir a data de inativação caso o cliente tenha sido inativado. Se uma minoria dos clientes forem inativados, o consumo de espaço em disco a mais será mínimo e você está armazenando mais informação neste caso. Saber a data de inativação pode ser importante para você no futuro. Existem outros casos em que ativo e inativo pode não ser suficientes, você pode querer ter mais valores possíveis para a sua coluna. Neste caso você poderia utilizar o tipo ENUM.

 

Leave a Reply

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.