Como evitar uma dependência cíclica (referência circular) entre 3 tabelas?

10

Eu tenho 3 tabelas:

  • Pessoas
  • Postar
  • Curtidas

Ao projetar o modelo ER, ele tem uma dependência cíclica:

         1: N
Pessoas -------- <Post

         1: N
Mensagem ---------- <Likes

         1: N
Pessoas -------- <Likes

A lógica é:

  • 1 pessoas podem ter muitos posts.

  • 1 post tem muitos likes.

  • 1 pessoa pode gostar de muitas postagens (a pessoa criada não pode gostar de sua própria postagem).

Como posso remover esse tipo de design cíclico? Ou meu design de db está errado?

Ragu
fonte

Respostas:

10

Regras do negócio

Vamos reformular as regras de negócios que você apresentou:

  • A Personcria zero-um-ou-muitos Posts .
  • A Postrecebe zero-um-muitos Likes .
  • Um Personmanifesta zero-um-ou-muitos Likes , cada um referente a um específico Post .

Modelos lógicos

Então, a partir desse conjunto de asserções, derivamos os dois modelos de dados IDEF1X [1] de nível lógico que são mostrados na Figura 1 .

Figura 1 - Modelos de dados de pessoas e publicações

Opção A

Como você pode ver no modelo uma opção, PersonId migra [2] a partir Personde Postcomo uma chave estrangeira (FK), mas ele recebe o nome da função [3] de AuthorId, e este atributo torna-se, juntamente com PostNumber, a chave primária (PK) do Posttipo de entidade.

Presumo que um Likesó pode existir em conexão com um determinado Post, então eu configurar uma LikePK que é composto por três atributos diferentes: PostAuthorId, PostNumbere LikerId. A combinação de PostAuthorIde PostNumberé um FK que faz a referência adequada ao PostPK. LikerIdé, por sua vez, um FK que estabelece a associação adequada com Person.PersonId.

Com o auxílio dessa estrutura, você garante que uma determinada pessoa possa manifestar apenas uma única Likeocorrência na mesma Postinstância.

Métodos para impedir que um autor da postagem curta sua própria publicação

Como você não deseja permitir a possibilidade de uma pessoa gostar de suas postagens de autoria, uma vez na fase de implementação, você deve estabelecer um método que compare o valor Like.PostAuthorIdcom o valor de Like.LikerIdtodas as tentativas do INSERT. Se esses valores corresponderem, (a) você rejeitará a inserção; se eles não corresponderem (b), você deixará o processo continuar.

Para realizar esta tarefa no seu banco de dados, você pode fazer uso de:

  1. A CHECK CONSTRAINT mas, é claro, esse método exclui o MySQL, pois ele não foi implementado nesta plataforma até agora, como você pode ver aqui e aqui .

  2. Codifique linhas dentro de uma transação ACID .

  3. Codifique linhas dentro de um TRIGGER , que poderia retornar uma mensagem personalizada indicando a tentativa de violação da regra.

Opção B

Se o autor não for um atributo que identifique de maneira primária uma postagem em seu domínio comercial, você poderá usar uma estrutura semelhante à descrita na Opção B.

Essa abordagem também garante que uma postagem possa ser curtida apenas pela mesma pessoa uma única vez.


Notas

1. A Definição de Integração para Modelagem de Informações ( IDEF1X ) é uma técnica de modelagem de dados altamente recomendável que foi definida como padrão em dezembro de 1993 pelo Instituto Nacional de Padrões e Tecnologia dos Estados Unidos ( NIST ).

2. O IDEF1X define a migração de chave como “O processo de modelagem de colocar a chave primária de uma entidade pai ou genérica em seu filho ou entidade de categoria como uma chave estrangeira”.

3. Um nome de função é uma denotação atribuída a um atributo de chave estrangeira para expressar o significado desse atributo no contexto de seu tipo de entidade correspondente. A nomeação de funções é recomendada desde 1970 pelo Dr. EF Codd em seu artigo seminal intitulado "Um modelo relacional de dados para grandes bancos de dados compartilhados" . Por sua vez, o IDEF1X - mantendo a fidelidade em relação às práticas relacionais - também defende esse procedimento.

MDCCL
fonte
6

Não vejo nada sendo cíclico aqui. Existem pessoas e cargos e dois relacionamentos independentes entre essas entidades. Eu veria gostos como a implementação de um desses relacionamentos.

  • Uma pessoa pode escrever muitas postagens, uma postagem é escrita por uma pessoa: 1:n
  • Uma pessoa pode gostar de muitos posts, um post pode ser apreciado por muitas pessoas: n:m
    O n: relacionamento m pode ser implementado com outra relação: likes.

Implementação básica

A implementação básica pode ser assim no PostgreSQL :

CREATE TABLE person (
  person_id serial PRIMARY KEY
, person    text NOT NULL
);

CREATE TABLE post (
  post_id   serial PRIMARY KEY
, author_id int NOT NULL  -- cannot be anonymous
     REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE  -- 1:n relationship
, post      text NOT NULL
);

CREATE TABLE likes (  -- n:m relationship
  person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE
, post_id   int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE
, PRIMARY KEY (post_id, person_id)
);

Observe, em particular, que uma postagem deve ter um autor ( NOT NULL), enquanto a existência de curtidas é opcional. No entanto, para curtidas existentes, poste ambas person devem ser referenciadas (impingidas pela PRIMARY KEYque cria as duas colunas NOT NULLautomaticamente (você pode adicionar essas restrições de forma explícita e redundante), para que curtidas anônimas também sejam impossíveis.

Detalhes para a implementação n: m:

Evitar auto-como

Você também escreveu:

(a pessoa criada não pode gostar de sua própria postagem).

Isso ainda não é imposto na implementação acima. Você poderia usar um gatilho .
Ou uma dessas soluções mais rápidas / confiáveis:

Sólido por um custo

Se ele precisa ser sólida , você pode estender o FK partir likespara postincluir a author_idforma redundante. Então você pode excluir o incesto com uma CHECKrestrição simples .

CREATE TABLE likes (
  person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE
, post_id   int 
, author_id int NOT NULL
, CONSTRAINT likes_pkey PRIMARY KEY (post_id, person_id)
, CONSTRAINT likes_post_fkey FOREIGN KEY (author_id, post_id)
     REFERENCES post(author_id, post_id) ON UPDATE CASCADE ON DELETE CASCADE
, CONSTRAINT no_self_like CHECK (person_id <> author_id)
);

Isso requer uma UNIQUErestrição também redundante em post:

ALTER TABLE post ADD CONSTRAINT post_for_fk_uni UNIQUE (author_id, post_id);

Eu coloquei o author_idprimeiro a fornecer um índice útil enquanto estava nele.

Resposta relacionada com mais:

Mais barato com uma CHECKrestrição

Com base na "Implementação básica" acima.

CHECKrestrições são imutáveis. Fazer referência a outras tabelas para uma verificação nunca é imutável, estamos abusando um pouco do conceito aqui. Sugiro declarar a restrição NOT VALIDpara refletir isso adequadamente. Detalhes:

Uma CHECKrestrição parece razoável nesse caso específico, porque o autor de uma postagem parece um atributo que nunca muda. Não permita atualizações nesse campo para ter certeza.

Nós fingimos uma IMMUTABLEfunção:

CREATE OR REPLACE FUNCTION f_author_id_of_post(_post_id int)
  RETURNS int AS
'SELECT p.author_id FROM public.post p WHERE p.post_id = $1'
LANGUAGE sql IMMUTABLE;

Substitua 'public' pelo esquema real de suas tabelas.
Use esta função em uma CHECKrestrição:

ALTER TABLE likes ADD CONSTRAINT no_self_like_chk
   CHECK (f_author_id_of_post(post_id) <> person_id) NOT VALID;
Erwin Brandstetter
fonte
4

Acho que você está tendo dificuldades para descobrir isso por causa de como você define suas regras de negócios.

Pessoas e mensagens são "objetos". Like é um verbo.

Você tem realmente apenas duas ações:

  1. Uma pessoa pode criar uma ou mais postagens
  2. Muitas pessoas podem gostar de muitos posts. (uma compilação das suas duas últimas declarações)

pessoas como diagrama de postagens

A tabela "likes" terá person_id e post_id como chave primária.

Nicolas de Fontenay
fonte