Você pode implementar isso sem truques sujos . Basta estender a chave estrangeira referenciando a opção escolhida para incluir
variable_id
além de choice_id
. Aqui está uma demonstração de trabalho. Mesas temporárias, para que você possa jogar facilmente:
CREATE TABLE systemvariables (
variable_id int PRIMARY KEY
, choice_id int
, variable text
);
INSERT INTO systemvariables(variable_id, variable) VALUES
(1, 'var1')
, (2, 'var2')
, (3, 'var3')
;
CREATE TABLE variableoptions (
option_id int PRIMARY KEY
, variable_id int REFERENCES systemvariables ON UPDATE CASCADE ON DELETE CASCADE
, option text
, UNIQUE (option_id, variable_id) -- needed for the FK
);
ALTER TABLE systemvariables
ADD CONSTRAINT systemvariables_choice_id_fk
FOREIGN KEY (choice_id, variable_id) REFERENCES variableoptions(option_id, variable_id);
INSERT INTO variableoptions VALUES
(1, 'var1_op1', 1)
, (2, 'var1_op2', 1)
, (3, 'var1_op3', 1)
, (4, 'var2_op1', 2)
, (5, 'var2_op2', 2)
, (6, 'var3_op1', 3)
;
A escolha de uma opção associada é permitida:
UPDATE systemvariables SET choice_id = 2 WHERE variable_id = 1;
UPDATE systemvariables SET choice_id = 5 WHERE variable_id = 2;
UPDATE systemvariables SET choice_id = 6 WHERE variable_id = 3;
Mas não há como sair da linha:
UPDATE systemvariables SET choice_id = 7 WHERE variable_id = 3;
UPDATE systemvariables SET choice_id = 4 WHERE variable_id = 1;
ERROR: insert or update on table "systemvariables" violates foreign key constraint "systemvariables_choice_id_fk" DETAIL: Key (choice_id,variable_id)=(4,1) is not present in table "variableoptions".
Exatamente o que você queria.
Todas as colunas de chave NOT NULL
Acho que encontrei uma solução melhor nesta resposta posterior:
- Como lidar com inserções mutuamente dependentes
Respondendo à pergunta do @ypercube nos comentários, para evitar entradas com associação desconhecida, faça todas as colunas-chave
NOT NULL
, incluindo chaves estrangeiras. A dependência circular normalmente tornaria isso impossível. É o clássico ovo de galinha problema:um dos dois tem que estar lá primeiro para gerar o outro. Mas a natureza encontrou uma maneira de contornar isso, e o Postgres também:restrições de chave estrangeira adiáveis .
CREATE TABLE systemvariables (
variable_id int PRIMARY KEY
, variable text
, choice_id int NOT NULL
);
CREATE TABLE variableoptions (
option_id int PRIMARY KEY
, option text
, variable_id int NOT NULL REFERENCES systemvariables
ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
, UNIQUE (option_id, variable_id) -- needed for the foreign key
);
ALTER TABLE systemvariables
ADD CONSTRAINT systemvariables_choice_id_fk FOREIGN KEY (choice_id, variable_id)
REFERENCES variableoptions(option_id, variable_id) DEFERRABLE INITIALLY DEFERRED; -- no CASCADING here!
Novo variáveis e opções associadas devem ser inseridas na mesma transação:
BEGIN;
INSERT INTO systemvariables (variable_id, variable, choice_id)
VALUES
(1, 'var1', 2)
, (2, 'var2', 5)
, (3, 'var3', 6);
INSERT INTO variableoptions (option_id, option, variable_id)
VALUES
(1, 'var1_op1', 1)
, (2, 'var1_op2', 1)
, (3, 'var1_op3', 1)
, (4, 'var2_op1', 2)
, (5, 'var2_op2', 2)
, (6, 'var3_op1', 3);
END;
O
NOT NULL
restrição não pode ser adiada, ela é aplicada imediatamente. Mas a restrição de chave estrangeira pode , porque definimos dessa forma. Ele é verificado ao final da transação, o que evita o problema do ovo de galinha. Neste editado cenário, ambas as chaves estrangeiras são adiadas . Você pode inserir variáveis e opções em sequência arbitrária.
Você pode até fazê-lo funcionar com uma restrição FK simples e não adiável se você inserir entradas relacionadas em ambas as tabelas em uma instrução usando CTEs conforme detalhado na resposta vinculada.
Você deve ter notado que a primeira restrição de chave estrangeira não tem
CASCADE
modificador. (Não faria sentido permitir alterações em variableoptions.variable_id
para cascata de volta. Por outro lado, a segunda chave estrangeira tem um
CASCADE
modificador e é definido DEFERRABLE
Não obstante. Isso traz algumas limitações. O manual:
Ações referenciais que não sejamNO ACTION
a verificação não pode ser adiada, mesmo que a restrição seja declarada adiável.
NO ACTION
é o padrão. Portanto, verificações de integridade referencial em
INSERT
são adiadas, mas as ações em cascata declaradas em DELETE
e UPDATE
não são. O seguinte não é permitido no PostgreSQL 9.0 ou posterior porque as restrições são impostas após cada instrução:UPDATE option SET var_id = 4 WHERE var_id = 5;
DELETE FROM var WHERE var_id = 5;
Detalhes:
- A restrição definida DIFERÍVEL INICIALMENTE IMEDIATO ainda é DIFERIDA?