Se você não leu, recomendo que leia 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 esse tipo de dado e o armazenam como uma coluna de uma tabela.
Uma coluna booleana pode ter apenas três valores distintos: falso, verdadeiro ou nulo. O PostgreSQL possui três palavras reservadas para lidar com esses 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 de 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 vai sempre 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’ e ‘1’ como sinônimos para TRUE. Mas isso funciona dentro de um INSERT. Nesse contexto, o PostgreSQL faz uma coerção implícita para o tipo de dado correto. 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 12So 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, várias funções com o mesmo nome, recebendo parâmetros diferentes. Veja 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. Veja 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 de 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)Isto é, 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 com parâmetros diferentes.
Existe ainda um contexto específico 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 3Observação importante
Por último, um pequeno comentário sobre o uso de booleanos como tipo de dado 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 um caso em que você queira uma 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 for inativada, o consumo de espaço a mais em disco será mínimo, e você estará armazenando mais informação nesse caso. Saber a data de inativação pode ser importante para você no futuro.
Existem outros casos em que ativo e inativo podem não ser suficientes. Você pode querer ter mais valores possíveis para sua coluna, por exemplo. Nesse caso, você poderia utilizar o tipo ENUM.

Deixe um comentário