PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Restrição de chave estrangeira complexa no SQLAlchemy


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 sejam NO 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?