Você está usando a herança (também conhecida na modelagem entidade-relacionamento como "subclasse" ou "categoria"). Em geral, existem 3 maneiras de representá-lo no banco de dados:
- "Todas as classes em uma tabela": Tenha apenas uma tabela "cobrindo" o pai e todas as classes filho (ou seja, com todas as colunas pai e filho), com uma restrição CHECK para garantir que o subconjunto correto de campos não seja NULL (ou seja, dois filhos diferentes não "misturam").
- "Classe concreta por tabela": Tenha uma tabela diferente para cada filho, mas nenhuma tabela pai. Isso exige que os relacionamentos dos pais (no seu caso, Inventário <- Armazenamento) sejam repetidos em todos os filhos.
- "Classe por tabela": Ter uma tabela pai e uma tabela separada para cada filho, que é o que você está tentando fazer. Isso é mais limpo, mas pode custar algum desempenho (principalmente ao modificar dados, não tanto ao consultar, porque você pode ingressar diretamente do filho e pular o pai).
Eu geralmente prefiro a terceira abordagem, mas reforce tanto a presença e a exclusividade de uma criança no nível de aplicação. Impor ambos no nível do banco de dados é um pouco complicado, mas pode ser feito se o DBMS suportar restrições adiadas. Por exemplo:
CHECK (
(
(VAN_ID IS NOT NULL AND VAN_ID = STORAGE_ID)
AND WAREHOUSE_ID IS NULL
)
OR (
VAN_ID IS NULL
AND (WAREHOUSE_ID IS NOT NULL AND WAREHOUSE_ID = STORAGE_ID)
)
)
Isso aplicará a exclusividade (devido ao
CHECK
) e a presença (devido à combinação de CHECK
e FK1
/FK2
) da criança. Infelizmente, o MS SQL Server não oferece suporte a restrições adiadas, mas você pode "esconder" toda a operação por trás de procedimentos armazenados e proibir os clientes de modificar as tabelas diretamente.
Apenas a exclusividade pode ser aplicada sem restrições adiadas:
O
STORAGE_TYPE
é um discriminador de tipo, geralmente um inteiro para economizar espaço (no exemplo acima, 0 e 1 são "conhecidos" pelo seu aplicativo e interpretados de acordo). O
VAN.STORAGE_TYPE
e WAREHOUSE.STORAGE_TYPE
podem ser computadas (também conhecidas como colunas "calculadas") para economizar armazenamento e evitar a necessidade do CHECK
s. --- EDITAR ---
As colunas computadas funcionariam no SQL Server assim:
CREATE TABLE STORAGE (
STORAGE_ID int PRIMARY KEY,
STORAGE_TYPE tinyint NOT NULL,
UNIQUE (STORAGE_ID, STORAGE_TYPE)
);
CREATE TABLE VAN (
STORAGE_ID int PRIMARY KEY,
STORAGE_TYPE AS CAST(0 as tinyint) PERSISTED,
FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE)
);
CREATE TABLE WAREHOUSE (
STORAGE_ID int PRIMARY KEY,
STORAGE_TYPE AS CAST(1 as tinyint) PERSISTED,
FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE)
);
-- We can make a new van.
INSERT INTO STORAGE VALUES (100, 0);
INSERT INTO VAN VALUES (100);
-- But we cannot make it a warehouse too.
INSERT INTO WAREHOUSE VALUES (100);
-- Msg 547, Level 16, State 0, Line 24
-- The INSERT statement conflicted with the FOREIGN KEY constraint "FK__WAREHOUSE__695C9DA1". The conflict occurred in database "master", table "dbo.STORAGE".
Infelizmente, o SQL Server requer uma coluna computada que é usada em um externo chave a ser PERSISTIDA. Outros bancos de dados podem não ter essa limitação (por exemplo, colunas virtuais do Oracle), o que pode economizar algum espaço de armazenamento.