Por que você não tem uma chave estrangeira em uma associação polimórfica?
Pergunta
Por que você não tem uma chave estrangeira em uma associação polimórfica, como a representada abaixo como um modelo de Rails?
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class Article < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Photo < ActiveRecord::Base
has_many :comments, :as => :commentable
#...
end
class Event < ActiveRecord::Base
has_many :comments, :as => :commentable
end
Solução
Uma chave estrangeira deve referenciar apenas uma tabela pai. Isso é fundamental para ambos sintaxe SQL, ea teoria relacional.
uma associação polimórfica é quando uma dada coluna pode fazer referência a qualquer uma das duas ou mais tabelas pai. Não há nenhuma maneira você pode declarar essa restrição em SQL.
Associações O polimórficos projeto quebra as regras de design de banco de dados relacional. Eu não recomendo usá-lo.
Existem várias alternativas:
-
Exclusivo Arcs: Criar várias colunas de chave estrangeira, cada referência a um dos pais. Impor que exatamente uma dessas chaves estrangeiras pode ser não-NULL.
-
Inverta o relacionamento:. Use três muitos-para-muitos mesas, cada um referências comentários e um respectivo pai
-
Concrete Supertable: Em vez da superclasse implícita "commentable", criar uma tabela real que cada uma de suas referências tabelas pai. Em seguida, ligar os seus comentários para que supertable. Pseudo-rails código seria algo como o seguinte (eu não sou um usuário Rails, por isso, tratar isso como uma diretriz, não o código literal):
class Commentable < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :commentable end class Article < ActiveRecord::Base belongs_to :commentable end class Photo < ActiveRecord::Base belongs_to :commentable end class Event < ActiveRecord::Base belongs_to :commentable end
Eu também cobrir associações polimórficas na minha apresentação prática orientada a objeto modelos em SQL , e meu livro SQL antipadrões: evitar as armadilhas de programação de banco de dados .
Re seu comentário: Sim, eu sei que há uma outra coluna que notas o nome da tabela que a chave estrangeira supostamente aponta. Este projeto não é suportado por chaves estrangeiras no SQL.
O que acontece, por exemplo, se você inserir um comentário eo nome do "Video" como o nome da tabela pai para que Comment
? Nenhuma tabela chamado "Video" existe. Caso a inserção ser abortada com um erro? O que restrição está sendo violado? Como é que os RDBMS sabe que esta coluna é suposto para citar uma tabela existente? Como é que lidar com nomes de tabela maiúsculas e minúsculas?
Da mesma forma, se você deixar cair a tabela Events
, mas você tem linhas na Comments
que indicam eventos como o seu pai, o que deve ser o resultado? Caso a mesa de queda ser abortada? Deve linhas em Comments
ser órfãos? Eles devem mudar para se referir a uma outra tabela existente, como Articles
? Será que os valores id que usado para apontar para Events
faz qualquer sentido quando apontando para Articles
?
Estes dilemas são todos devido ao fato de que associações polimórficas depende da utilização de dados (ou seja, um valor string) para se referir a metadados (um nome de tabela). Isto não é suportado pelo SQL. Os dados e metadados são separados.
Eu estou tendo um momento difícil envolver minha cabeça em torno de seu "Concrete Supertable" proposta.
-
Definir
Commentable
como uma tabela SQL real, não apenas um adjetivo em sua definição de modelo de Rails. Há outras colunas são necessárias.CREATE TABLE Commentable ( id INT AUTO_INCREMENT PRIMARY KEY ) TYPE=InnoDB;
-
Defina a
Articles
tabelas,Photos
eEvents
como "subclasses" deCommentable
, fazendo sua chave primária também ser umCommentable
referência de chave estrangeira.CREATE TABLE Articles ( id INT PRIMARY KEY, -- not auto-increment FOREIGN KEY (id) REFERENCES Commentable(id) ) TYPE=InnoDB; -- similar for Photos and Events.
-
Definir tabela de
Comments
com uma chave estrangeira paraCommentable
.CREATE TABLE Comments ( id INT PRIMARY KEY AUTO_INCREMENT, commentable_id INT NOT NULL, FOREIGN KEY (commentable_id) REFERENCES Commentable(id) ) TYPE=InnoDB;
-
Quando você quiser criar um
Article
(por exemplo), você deve criar uma nova linha naCommentable
também. Assim também paraPhotos
eEvents
.INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 1 INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... ); INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 2 INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... ); INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 3 INSERT INTO Events (id, ...) VALUES ( LAST_INSERT_ID(), ... );
-
Quando você quiser criar um
Comment
, use um valor que existe emCommentable
.INSERT INTO Comments (id, commentable_id, ...) VALUES (DEFAULT, 2, ...);
-
Quando você quiser comentários de consulta de um determinado
Photo
, fazer alguma junta-se:SELECT * FROM Photos p JOIN Commentable t ON (p.id = t.id) LEFT OUTER JOIN Comments c ON (t.id = c.commentable_id) WHERE p.id = 2;
-
When você tem apenas o ID de um comentário e você quer encontrar o recurso commentable é um recado para. Para isso, você pode achar que é útil para a tabela commentable para designar qual recurso faz referência.
SELECT commentable_id, commentable_type FROM Commentable t JOIN Comments c ON (t.id = c.commentable_id) WHERE c.id = 42;
Em seguida, você precisa executar uma segunda consulta para obter dados a partir da tabela de recursos respectiva (fotos, artigos, etc.), depois de descobrir a partir
commentable_type
qual tabela para se juntar a. Você não pode fazê-lo na mesma consulta, porque o SQL requer que as tabelas ser chamado explicitamente; você não pode juntar-se a uma tabela determinada por resultados de dados na mesma consulta.
É verdade que alguns desses passos quebrar as convenções utilizadas pelo Rails. Mas as convenções Rails estão errados com relação ao projeto apropriado banco de dados relacional.
Outras dicas
Bill Karwin é correto que as chaves estrangeiras não pode ser usado com relacionamentos polimórficos devido ao SQL realmente não ter um conceito nativo relacionamentos polimórficos. Mas se seu objetivo de ter uma chave estrangeira é para impor a integridade referencial pode simulá-lo por meio de gatilhos. Isso fica DB específico, mas abaixo é alguns gatilhos recentes I criados para simular o comportamento de exclusão em cascata de uma chave estrangeira em um relacionamento polimórfico:
CREATE FUNCTION delete_related_brokerage_subscribers() RETURNS trigger AS $$
BEGIN
DELETE FROM subscribers
WHERE referrer_type = 'Brokerage' AND referrer_id = OLD.id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER cascade_brokerage_subscriber_delete
AFTER DELETE ON brokerages
FOR EACH ROW EXECUTE PROCEDURE delete_related_brokerage_subscribers();
CREATE FUNCTION delete_related_agent_subscribers() RETURNS trigger AS $$
BEGIN
DELETE FROM subscribers
WHERE referrer_type = 'Agent' AND referrer_id = OLD.id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER cascade_agent_subscriber_delete
AFTER DELETE ON agents
FOR EACH ROW EXECUTE PROCEDURE delete_related_agent_subscribers();
No meu código de registro na tabela brokerages
ou um registro na tabela de agents
pode se relacionar a um registro na tabela subscribers
.