2.4. Restrições

O tipo de dado é uma forma de limitar os dados que podem ser armazenados na tabela. Entretanto, para muitas aplicações esta restrição é abrangente demais. Por exemplo, uma coluna contendo preços de produtos normalmente só pode aceitar valores positivos, mas não existe nenhum tipo de dado que aceita apenas números positivos. Outro problema é que pode ser necessário restringir os dados de uma coluna com relação a outras colunas ou linhas. Por exemplo, em uma tabela contendo informações relativas aos produtos deve haver apenas uma linha contendo um determinado código de produto.

Para esta finalidade, a linguagem SQL permite definir restrições em colunas e tabelas. As restrições permitem o nível de controle que for necessário sobre os dados de uma tabela. Se o usuário tentar armazenar dados em uma coluna da tabela violando a restrição, ocasiona erro. Isto se aplica até quando o erro é originado pela definição do valor padrão.

2.4.1. Restrição de verificação

Uma restrição de verificação é o tipo mais genérico de restrição. Permite especificar que os valores de uma determinada coluna devem estar de acordo com uma expressão arbitrária. Por exemplo, pode ser utilizado para permitir apenas valores positivos para os preços:

CREATE TABLE produtos (
    id_produto integer,
    nome       text,
    preco      numeric CHECK (preco > 0)
);

Como pode ser observado, a definição da restrição está posicionada após o tipo de dado, do mesmo modo que a definição de valor padrão. O valor padrão e as restrições podem estar em qualquer ordem. Uma restrição de verificação é composto pela palavra chave CHECK seguida por uma expressão entre parênteses. A expressão de restrição de verificação deve envolver a coluna sendo restringida, senão não fará muito sentido.

Também pode ser atribuído um nome individual para a restrição. Isto torna mais clara a mensagem de erro, e permite fazer referência à restrição quando for desejado alterá-la. A sintaxe é:

CREATE TABLE produtos (
    id_produto integer,
    nome       text,
    preco      numeric CONSTRAINT preco_positivo CHECK (preco > 0)
);

Portanto, para especificar uma restrição com nome deve ser utilizada a palavra chave CONSTRAINT, seguida por um identificador, seguido por sua vez pela definição da restrição.

Uma restrição de verificação também pode referenciar várias colunas. Supondo que seja desejado armazenar o preço normal e o preço com desconto, e que seja necessário garantir que o preço com desconto seja menor que o preço normal:

CREATE TABLE produtos (
    id_produto         integer,
    nome               text,
    preco              numeric CHECK (preco > 0),
    preco_com_desconto numeric CHECK (preco_com_desconto > 0),
    CHECK (preco > preco_com_desconto)
);

As duas primeiras formas de restrição já devem ser familiares. A terceira utiliza uma nova sintaxe, que não está presa a nenhuma determinada coluna. Em vez disso, aparece como item à parte na lista de colunas separadas por vírgula. As definições das colunas e as definições destas restrições podem aparecer em qualquer ordem.

Dizemos que as duas primeiras restrições são restrições de coluna, enquanto a terceira é uma restrição de tabela, porque está escrita separado das definições de colunas. As restrições de coluna também podem ser escritas como restrições de tabela, enquanto o contrário nem sempre é possível. O exemplo acima também pode ser escrito do seguinte modo

CREATE TABLE produtos (
    id_produto         integer,
    nome               text,
    preco              numeric,
    CHECK (preco > 0),
    preco_com_desconto numeric,
    CHECK (preco_com_desconto > 0),
    CHECK (preco > preco_com_desconto)
);

ou ainda

CREATE TABLE produtos (
    id_produto         integer,
    nome               text,
    preco              numeric CHECK (preco > 0),
    preco_com_desconto numeric,
    CHECK (preco_com_desconto > 0 AND preco > preco_com_desconto)
);

É uma questão de gosto.

Deve ser observado que a expressão de verificação está satisfeita se o resultado desta expressão for verdade ou se for nulo. Uma vez que quase todas as expressões retornam um resultado nulo quando um dos operandos é nulo, estas expressões não impedem a presença de valores nulos nas colunas com restrição. Para garantir que uma coluna não aceita o valor nulo, deve ser utilizada a restrição de não nulo descrita a seguir.

2.4.2. Restrição de não-nulo

Uma restrição de não-nulo simplesmente especifica que uma coluna não pode conter o valor nulo. Um exemplo da sintaxe:

CREATE TABLE produtos (
    id_produto integer NOT NULL,
    nome       text    NOT NULL,
    preco      numeric
);

Uma restrição de não-nulo é sempre escrita como restrição de coluna. Uma restrição de não-nulo é funcionalmente equivalente a uma restrição de verificação CHECK (nome_da_coluna IS NOT NULL), mas no PostgreSQL a criação de uma restrição de não-nulo explícita é mais eficiente. A desvantagem é não poder ser dado um nome explícito para uma restrição criada deste modo.

Uma coluna pode possuir mais de uma restrição, bastando simplesmente escrever uma restrição após a outra:

CREATE TABLE produtos (
    id_produto integer NOT NULL,
    nome       text    NOT NULL,
    preco      numeric NOT NULL CHECK (preco > 0)
);

A ordem das restrições não importa, porque não afeta, necessariamente, a ordem pela qual as restrições são verificadas.

A restrição NOT NULL possui uma inversa: a restrição NULL. Isto não significa que a coluna deve ser nula, que com certeza não tem utilidade. Em vez disto é simplesmente definido o comportamento padrão dizendo que a coluna pode ser nula. A restrição NULL não é definida no padrão SQL, e não deve ser utilizada em aplicações portáveis (somente foi adicionada ao PostgreSQL para torná-lo compatível com outros sistemas de banco de dados). Porém, alguns usuários gostam porque torna fácil inverter a restrição no arquivo de comandos. Por exemplo, é possível começar com

CREATE TABLE produtos (
    id_produto integer NULL,
    nome       text    NULL,
    preco      numeric NULL
);

e depois colocar a palavra chave NOT onde for desejado.

Dica: Na maioria dos projetos de banco de dados, a maioria das colunas deve ser especificada como não-nula.

2.4.3. Restrição de unicidade

A restrição de unicidade garante que os dados contidos na coluna, ou no grupo de colunas, é único em relação a todas as outras linhas da tabela. A sintaxe é

CREATE TABLE produtos (
    id_produto integer UNIQUE,
    nome       text,
    preco      numeric
);

quando escrita como restrição de coluna, e

CREATE TABLE produtos (
    id_produto integer,
    nome       text,
    preco      numeric,
    UNIQUE (id_produto)
);

quando escrita como restrição de tabela.

Se uma restrição de unicidade faz referência a um grupo de colunas, as colunas da lista são separadas por vírgula:

CREATE TABLE exemplo (
    a integer,
    b integer,
    c integer,
    UNIQUE (a, c)
);

Também é possível atribuir nomes às restrições de unicidade:

CREATE TABLE produtos (
    id_produto integer CONSTRAINT deve_ser_diferente UNIQUE,
    nome       text,
    preco      numeric
);

De um modo geral, uma restrição de unicidade é violada quando existem (pelo menos) duas linhas na tabela onde os valores de cada uma das colunas correspondentes, que fazem parte da restrição, são iguais. Entretanto, os valores nulos não são considerados iguais nesta situação. Isto significa que, na presença de uma restrição de unicidade multicolunas, é possível armazenar-se um número ilimitado de linhas, se estas linhas contiverem um valor nulo em pelo menos uma das colunas da restrição. Este comando está em conformidade com o padrão SQL, mas já ouvimos dizer que outros bancos de dados SQL não seguem esta regra. Portanto, seja cauteloso ao desenvolver aplicações onde se pretenda haver portabilidade.

2.4.4. Chave primária

Tecnicamente uma chave primária é simplesmente a combinação da restrição de unicidade com a restrição de não-nulo. Portanto, as duas definições de tabela abaixo aceitam os mesmos dados:

CREATE TABLE produtos (
    id_produto integer UNIQUE NOT NULL,
    nome       text,
    preco      numeric
);

CREATE TABLE produtos (
    id_produto integer PRIMARY KEY,
    nome       text,
    preco      numeric
);

As chaves primárias também podem abranger mais de uma coluna; a sintaxe é similar a das restrições de unicidade:

CREATE TABLE exemplo (
    a integer,
    b integer,
    c integer,
    PRIMARY KEY (a, c)
);

A chave primária indica que a coluna, ou grupo de colunas, pode ser utilizada como identificador único para as linhas da tabela (Uma conseqüência direta da definição da chave primária. Observe que a restrição de unicidade na verdade não fornece um identificador único, porque não exclui os valores nulos). Isto é útil tanto para fins de documentação quanto para aplicações cliente. Por exemplo, uma interface gráfica que permite modificar os valores das linhas provavelmente necessita conhecer a chave primária da tabela para poder identificar as linhas.

Uma tabela pode ter no máximo uma chave primária (embora possa ter muitas restrições de unicidade e de não-nulo). A teoria de banco de dados relacional determina que toda tabela deve ter uma chave primária. Esta regra não é obrigatória no PostgreSQL, mas normalmente é bom segui-la.

2.4.5. Chave estrangeira

A restrição de chave estrangeira especifica que o valor da coluna (ou grupo de colunas) deve corresponder a algum valor que existe em uma linha de outra tabela. Diz-se que este comportamento mantém a integridade referencial entre duas tabelas relacionadas.

Supondo que já temos a tabela de produtos utilizada diversas vezes anteriormente:

CREATE TABLE produtos (
    id_produto integer PRIMARY KEY,
    nome       text,
    preco      numeric
);

Agora vamos supor, também, que existe uma tabela armazenando os pedidos destes produtos, e desejamos garantir que a tabela de pedidos somente contenha pedidos de produtos que realmente existem. Para isso é definida uma restrição de chave estrangeira na tabela pedidos, fazendo referência à tabela produtos:

CREATE TABLE pedidos (
    id_pedido  integer PRIMARY KEY,
    id_produto integer REFERENCES produtos (id_produto),
    quantidade integer
);

Isto torna impossível criar pedidos com ocorrências de id_produto que não existam na tabela produtos.

Nesta situação é dito que a tabela pedidos é a tabela que faz referência, e a tabela produtos é a tabela referenciada. Da mesma forma existem colunas fazendo referência e sendo referenciadas.

O comando acima pode ser abreviado escrevendo-se

CREATE TABLE pedidos (
    id_pedido  integer PRIMARY KEY,
    id_produto integer REFERENCES produtos,
    quantidade integer
);

porque na ausência da lista de colunas, a chave primária da tabela referenciada é assumida como sendo a coluna referenciada.

A chave estrangeira também pode conter e referenciar um grupo de colunas. Como usual, é necessário escrever na forma de restrição de tabela. Abaixo está mostrado um exemplo artificial da sintaxe:

CREATE TABLE t1 (
  a integer PRIMARY KEY,
  b integer,
  c integer,
  FOREIGN KEY (b, c) REFERENCES outra_tabela (c1, c2)
);

Obviamente, o número e o tipo das colunas na restrição precisam corresponder ao número e tipo das colunas referenciadas.

Uma tabela pode conter mais de uma restrição de chave estrangeira, utilizado para implementar relacionamentos muitos-para-muitos entre tabelas. Supondo que existam as tabelas produto e pedidos, e desejamos permitir que um pedido contenha vários produtos (o que não é possível na estrutura acima) podemos, então, utilizar a seguinte estrutura de tabela:

CREATE TABLE produtos (
    id_produto integer PRIMARY KEY,
    nome       text,
    preco      numeric
);

CREATE TABLE pedidos (
    id_pedido integer PRIMARY KEY,
    endereco_entrega text,
    ...
);

CREATE TABLE itens_pedidos (
    id_produto integer REFERENCES produtos,
    id_pedido  integer REFERENCES pedidos,
    quantidade integer,
    PRIMARY KEY (id_produto, id_pedido)
);

Observe, também, que a chave primária se sobrepõe às chaves estrangeiras na última tabela.

Sabemos que a chave estrangeira não permite a criação de pedidos sem relacionamento com um produto. Porém, o que acontece se um produto for removido após a criação de um pedido fazendo referência a este produto? A linguagem SQL permite a especificação desta situação também. Intuitivamente temos algumas opções:

Para ilustrar esta situação, vamos implementar a seguinte política no exemplo muitos-para-muitos acima: Quando alguém desejar excluir um produto referenciado por um pedido (através de itens_pedidos), não será permitido. Se alguém excluir um pedido, os itens do pedido também serão removidos.

CREATE TABLE produtos (
    id_produto integer PRIMARY KEY,
    nome       text,
    preco      numeric
);

CREATE TABLE pedidos (
    id_pedido        integer PRIMARY KEY,
    endereco_entrega text,
    ...
);

CREATE TABLE pedido_itens (
    id_produto integer REFERENCES produtos ON DELETE RESTRICT,
    id_pedido  integer REFERENCES pedidos  ON DELETE CASCADE,
    quantidade integer,
    PRIMARY KEY (id_produto, id_pedido)
);

Restringir ou excluir em cascata são as duas opções mais comuns. RESTRICT também pode ser escrito na forma NO ACTION, e também é o padrão se nada for especificado. Existem duas outras opções relativas ao que deve acontecer com as colunas da chave estrangeira quando a chave primária é excluída: SET NULL e SET DEFAULT. Observe que isto não livra da obediência às restrições. Por exemplo, se uma ação especificar SET DEFAULT, mas o valor padrão não satisfizer a chave estrangeira, a exclusão da chave primária vai falhar.

Semelhante a ON DELETE existe também ON UPDATE, chamada quando uma chave primária é alterada. As ações possíveis são as mesmas.

Mais informações relativas à atualização e a exclusão de dados podem ser encontradas no Capítulo 3.

Para terminar, devemos mencionar que a chave estrangeira deve referenciar colunas de uma chave primária ou de uma restrição de unicidade. Se a chave estrangeira fizer referência a uma restrição de unicidade, existem algumas possibilidades adicionais com relação a como os valores nulos são tratados. Esta parte está explicada na entrada CREATE TABLE no Manual de Referência do PostgreSQL.