O PostgreSQL implementa a herança de tabelas, que pode ser uma ferramenta útil para projetistas de banco de dados. (O SQL:1999 e posteriores definem um recurso de herança de tipo, que difere em muitos aspectos dos recursos descritos aqui.)
Vamos começar com um exemplo: suponha que estamos tentando construir
um padrão de dados para cidades. Cada estado tem muitas cidades, mas
apenas uma é a capital. Queremos conseguir buscar rapidamente
a capital de qualquer estado em particular. Isso pode ser feito
criando duas tabelas, uma para as capitais dos estados e outra para
as cidades que não são capitais. Entretanto, o que acontece quando
queremos buscar dados sobre uma cidade, independentemente de ser uma
capital ou não? O recurso de herança pode ajudar a resolver esse
problema. Definimos a tabela capitais de
forma que ela herde de cidades:
CREATE TABLE cidades (
nome text,
populacao float,
altitude int -- (em pés)
);
CREATE TABLE capitais (
estado char(2)
) INHERITS (cidades);
Neste caso, a tabela capitais
herda todas as colunas de sua tabela-mãe
cidades. As capitais dos estados possuem uma
coluna extra, estado, mostrando a qual
estado pertencem.
No PostgreSQL, uma tabela pode herdar de zero ou mais outras tabelas, e uma consulta pode referenciar todas as linhas da tabela, ou todas as linhas da tabela mais todas as linhas de suas tabelas descendentes. O último comportamento é o padrão. Por exemplo, a consulta a seguir encontra os nomes de todas as cidades, incluindo as capitais dos estados, localizadas a uma altitude acima de 500 pés:
SELECT nome, altitude
FROM cidades
WHERE altitude > 500;
Usando os dados de exemplo do tutorial do PostgreSQL (veja a Seção 2.1), esta consulta retorna:
nome | altitude -----------+---------- Las Vegas | 2174 Mariposa | 1953 Madison | 845 (3 linhas)
Por outro lado, a consulta a seguir encontra todas as cidades que não são capitais de estado e estão situadas a uma altitude acima de 500 pés:
SELECT nome, altitude
FROM ONLY cidades
WHERE altitude > 500;
nome | altitude
-----------+----------
Las Vegas | 2174
Mariposa | 1953
(2 linhas)
Aqui, a palavra-chave ONLY indica que a consulta
deve ser aplicada apenas à tabela cidades,
e não a quaisquer tabelas abaixo de cidades
na hierarquia de herança. Muitos comandos que já discutimos —
SELECT, UPDATE e
DELETE — aceitam a palavra-chave
ONLY.
O nome da tabela também pode ser escrito com *
à direita para especificar, explicitamente, que as tabelas
descendentes devem ser incluídas:
SELECT nome, altitude
FROM cidades*
WHERE altitude > 500;
Não é necessário escrever *, porque este comportamento
é sempre o padrão. Entretanto, esta sintaxe ainda tem suporte para
compatibilidade com versões mais antigas em que o padrão pode ser alterado.
Em alguns casos se deseja saber de qual tabela uma determinada linha
se originou. Existe uma coluna do sistema chamada
tableoid, em cada tabela, que pode
informar a tabela de origem da linha:
SELECT c.tableoid, c.nome, c.altitude FROM cidades c WHERE c.altitude > 500;
que retorna:
tableoid | nome | altitude
----------+-----------+----------
16647 | Las Vegas | 2174
16647 | Mariposa | 1953
16652 | Madison | 845
(3 linhas)
(Se você tentar reproduzir este exemplo, com certeza obterá números
diferentes para os OIDs.) Fazendo a junção da tabela
cidades com a tabela
pg_class podem ser obtidos os nomes
das tabelas:
SELECT p.relname, c.nome, c.altitude FROM cidades c, pg_class p WHERE c.altitude > 500 AND c.tableoid = p.oid;
que retorna:
relname | nome | altitude ----------+-----------+---------- cidades | Las Vegas | 2174 cidades | Mariposa | 1953 capitais | Madison | 845 (3 linhas)
Outra maneira de obter o mesmo resultado é usando o tipo alias para
OID regclass, que mostra textualmente o OID da tabela
ao se fazer a conversão de tipo de dados:
SELECT c.tableoid::regclass, c.nome, c.altitude FROM cidades c WHERE c.altitude > 500;
que também retorna:
tableoid | nome | altitude ----------+-----------+---------- cidades | Las Vegas | 2174 cidades | Mariposa | 1953 capitais | Madison | 845 (3 linhas)
A herança não propaga automaticamente os dados dos comandos
INSERT e COPY para outras
tabelas na hierarquia de herança. No nosso exemplo, o comando
INSERT abaixo irá falhar:
INSERT INTO cidades (nome, populacao, altitude, estado)
VALUES ('Albany', NULL, NULL, 'NY');
ERRO: não existe a coluna "populacao" na relação "cidades"
LINHA 1: INSERT INTO cidades (nome, populacao, altitude, estado)
^
Podemos esperar que os dados sejam de alguma forma roteados para a
tabela capitais, mas isso não acontece:
o comando INSERT sempre insere exatamente na
tabela especificada. Em alguns casos é possível redirecionar a
inserção usando uma regra (veja o Capítulo 39).
No entanto, isso não ajuda no caso acima, porque a tabela
cidades não contém a coluna
estado, portanto o comando será
rejeitado antes que a regra possa ser aplicada.
Todas as restrições de verificação e de não nulo em uma tabela-mãe
são herdadas automaticamente por suas tabelas-filhas, a menos que
seja especificado explicitamente de outra forma com cláusulas
NO INHERIT. Outros tipos de restrições
(restrições de unicidade, de chave primária e de chave estrangeira)
não são herdadas.
Uma tabela pode herdar de mais de uma tabela, recebendo nesse caso a união das colunas definidas nas tabelas-mães. Quaisquer colunas declaradas na definição da tabela-filha são adicionadas a ela. Se o mesmo nome de coluna aparecer em várias tabelas-mães, ou em uma tabela-mãe e na definição da tabela-filha, estas colunas são “mescladas” para haver apenas uma destas colunas na tabela-filha. Para serem mescladas, as colunas devem ter os mesmos tipos de dados, senão será gerado um erro. As restrições de verificação herdáveis e as restrições de não nulo são mescladas de maneira semelhante. Assim, por exemplo, uma coluna mesclada será marcada como não nula se qualquer uma das definições de coluna de onde veio estiver marcada como não nula. As restrições de verificação são mescladas se tiverem o mesmo nome e a mesclagem falhará se suas condições forem diferentes.
A herança de tabela é normalmente estabelecida quando a tabela-filha
é criada usando a cláusula INHERITS do comando
CREATE TABLE.
Como alternativa, uma tabela já definida de forma compatível
pode ter um novo relacionamento mãe adicionado, usando a forma
alternativa INHERIT do comando
ALTER TABLE.
Para fazer isso, a nova tabela-filha já deve ter colunas com os
mesmos nomes e tipos de dados das colunas da tabela-mãe.
Também deve incluir restrições de verificação com os mesmos nomes e
expressões de verificação da tabela-mãe.
Da mesma forma, uma ligação de herança pode ser removida de uma filha
usando a forma alternativa NO INHERIT de
ALTER TABLE.
Adicionar e remover dinamicamente ligações de herança como esta pode
ser útil quando o relacionamento de herança está sendo usado para
particionamento de tabela (veja a Seção 5.12).
Uma forma conveniente para criar uma tabela compatível que mais
tarde se tornará uma tabela-filha é usando a cláusula
LIKE no comando CREATE TABLE.
Isso cria uma tabela com as mesmas colunas da tabela de origem.
Se houver alguma restrição CHECK definida na
tabela de origem, deve ser especificada a opção
INCLUDING CONSTRAINTS da cláusula
LIKE, porque a nova filha deve ter restrições
que correspondam às da mãe para ser considerada compatível.
Uma tabela-mãe não pode ser excluída enquanto qualquer uma de suas
filhas ainda existir. As colunas ou restrições de verificação das
tabelas-filhas também não podem ser excluídas ou alteradas se forem
herdadas de qualquer tabela-mãe. Se for desejado excluir uma tabela
juntamente com todas as suas tabelas descendentes, uma maneira fácil
é excluir a tabela-mãe usando a opção CASCADE
(veja Seção 5.15).
O comando ALTER TABLE propaga quaisquer
alterações nas definições de dados da coluna e verifica as
restrições na hierarquia de herança. Novamente, excluir colunas que
dependem de outras tabelas só é possível usando a opção
CASCADE. O comando ALTER TABLE
segue as mesmas regras para mesclagem e rejeição de colunas duplicadas
aplicadas ao executar CREATE TABLE.
As consultas herdadas executam verificações de permissão de acesso
apenas na tabela-mãe. Assim, por exemplo, conceder permissão
UPDATE na tabela cidades
implica na permissão para atualizar as linhas da tabela
capitais também, quando são acessadas por
meio de cidades. Isso preserva a aparência
de que os dados estão (também) na tabela-mãe. Mas a tabela
capitais não pode ser atualizada diretamente
sem uma concessão adicional. De maneira semelhante, as políticas de
segurança de linha da tabela-mãe
(veja a Seção 5.9) são aplicadas a linhas
provenientes de tabelas-filhas durante uma consulta herdada.
As políticas de uma tabela-filha, se houver, são aplicadas somente
quando a tabela é explicitamente referenciada na consulta; e, nesse
caso, quaisquer políticas vinculadas à(s) mãe(s) serão ignoradas.
As tabelas estrangeiras (veja a Seção 5.13) também podem fazer parte de hierarquias de herança, como tabelas mãe ou filha, da mesma forma que as tabelas regulares. Se uma tabela estrangeira fizer parte de uma hierarquia de herança, quaisquer operações sem suporte pela tabela estrangeira também não terão suporte em toda a hierarquia.
Note que nem todos os comandos SQL
funcionam em hierarquias de herança. Comandos usados para consulta
de dados, modificação de dados ou modificação de esquema
(por exemplo, SELECT, UPDATE,
DELETE, a maioria das formas alternativas de
ALTER TABLE, mas não INSERT ou
ALTER TABLE ... RENAME), incluem normalmente
as tabelas-filhas e aceitam a notação ONLY para
excluí-las.
A maioria dos comandos que realizam manutenção e otimização de banco
de dados (por exemplo, REINDEX) operam apenas em
tabelas físicas individuais e não têm suporte para recursão em
hierarquias de herança.
Entretanto, os comandos VACUUM e
ANALYZE incluem por padrão as tabelas-filhas e
têm suporte para a notação ONLY para permitir que
as tabelas-filhas não sejam incluídas.
O comportamento individual de cada comando está documentado
na sua página de referência (veja SQL Commands).
Uma limitação séria do recurso de herança, é que os índices (incluindo restrições de unicidade) e restrições de chave estrangeira só se aplicam a uma única tabela, não sendo aplicados às suas filhas na hierarquia de herança. Isto é verdade tanto no lado que faz a referência, quanto no lado que é referenciado, em uma restrição de chave estrangeira. Assim, nos termos do exemplo acima:
Se declararmos
cidades.nome
como sendo UNIQUE ou PRIMARY KEY,
isto não vai impedir que a tabela capitais
tenha linhas com nomes duplicados com relação à tabela
cidades.
E estas linhas duplicadas são mostradas, por padrão, nas consultas
de cidades.
Na verdade, por padrão, capitais não
teria nenhuma restrição de unicidade, portanto poderia conter
várias linhas com o mesmo nome de capital.
É possível adicionar uma restrição de unicidade a
capitais, mas isto não vai impedir a
existência de linhas duplicadas com relação a
cidades.
Da mesma forma, se declararmos que
cidades.nome
REFERENCES alguma outra tabela, esta restrição
não vai se propagar automaticamente para capitais.
Neste caso, isto pode ser contornado adicionando manualmente a
mesma restrição REFERENCES à tabela
capitais.
Especificar que uma coluna de outra tabela
REFERENCES cidades(nome), permitirá que a outra
tabela tenha os nomes das cidades, mas não os nomes das capitais.
Não existe uma boa solução para este caso.
Algumas funcionalidades não implementadas para hierarquias de herança são implementadas para particionamento declarativo. É necessário um cuidado considerável ao decidir se o particionamento com herança legada é útil para sua aplicação.