Como devo criar uma tabela de relacionamento para amizade?

33

Se Aé amigo B, devo guardar os dois valores ABe BA, ou um é suficiente? Quais são as vantagens e desvantagens de ambos os métodos.

Aqui está minha observação:

  • Se eu mantiver os dois, preciso atualizá-los quando receber uma solicitação de um amigo.
  • Se eu não mantiver os dois, será difícil ter que fazer vários JOINcom esta tabela.

Atualmente, mantenho o relacionamento de uma maneira.

insira a descrição da imagem aqui

Então, o que devo fazer neste caso? Algum conselho?

Chan
fonte
Você está comprometido com uma plataforma ou esta é uma questão teórica?
precisa saber é o seguinte
Que tal uma abordagem híbrida: Modelo correspondido e amizades unilaterais, respectivamente, em mesas separadas, garantir uma amizade é inserido exatamente uma dessas tabelas, não é agradável para alcançar o uso de produtos SQL de hoje :(
onedaywhen
@ onedaywhen - Sim, parece mais apropriado para um banco de dados de gráficos .
precisa saber é o seguinte
@NickChammas: Não é uma questão teórica. Estou trabalhando no mysqlqual está armazenado na nuvem da Amazon.
Chan
1
@Chan - Ah Isso significa que você não pode usar restrições de verificação para fazer cumprir a relação só é armazenada uma maneira então (MySQL não impõe estes)
Martin Smith

Respostas:

30

Eu armazenaria AB e BA. Uma amizade é realmente um relacionamento de mão dupla, cada entidade está ligada a outra. Embora intuitivamente pensemos na "amizade" como um elo entre duas pessoas, do ponto de vista relacional, é mais como "A tem um amigo B" e "B tem um amigo A". Dois relacionamentos, dois registros.

datagod
fonte
3
Muito Obrigado. Eu realmente preciso pensar na sua ideia com cuidado! O motivo de eu evitar armazenar AB e BA é por causa do armazenamento, pois cada vez que tenho uma amizade, minha mesa armazena o dobro.
Chan
1
Você está certo sobre o armazenamento, mas lembre-se de que, se armazenado como números inteiros, cada relacionamento amigo-amigo levaria cerca de 30 bytes (2 registros x 3 colunas x 4 bytes por número inteiro = 24 bytes mais algum preenchimento). 1 milhão de pessoas com 10 amigos cada uma ainda teria apenas cerca de 300 MB de dados.
datagod
1
datagod: isso mesmo!
Chan
Foi assim que desenhei minhas mesas também, a AB & BA.
kabuto178
2
Além disso, em situações em que há apenas AB e não BA, isso pode representar uma 'solicitação de amizade pendente'.
Greg
13

Se a amizade tiver a intenção de ser simétrica (ou seja, não é possível A ser amiga, Bmas não vice-versa), eu armazenaria apenas o relacionamento unidirecional com uma restrição de verificação, garantindo que cada relacionamento possa ser representado apenas de uma maneira.

Também abandonaria o ID substituto e, em vez disso, teria uma PK composta (e possivelmente um índice exclusivo composto também nas colunas invertidas).

CREATE TABLE Friends
  (
     UserID1 INT NOT NULL REFERENCES Users(UserID),
     UserID2 INT NOT NULL REFERENCES Users(UserID),
     CONSTRAINT CheckOneWay CHECK (UserID1 < UserID2),
     CONSTRAINT PK_Friends_UserID1_UserID2 PRIMARY KEY (UserID1, UserID2),
     CONSTRAINT UQ_Friends_UserID2_UserID1 UNIQUE (UserID2, UserID1)
  ) 

Você não diz as consultas que dificultam, mas sempre pode criar uma Visualização

CREATE VIEW Foo
AS
SELECT UserID1,UserID2 
FROM Friends
UNION ALL
SELECT UserID2,UserID1 
FROM Friends
Martin Smith
fonte
Eu sei que isso é bastante antigo, desculpe por desenterrar isso. Não seria melhor NÃO definir o índice inverso de amizade UNIQUE, a fim de não sobrecarregar desnecessariamente e redundantemente INSERTs? Como temos PRIMARY KEY (a,b)e como é um PK UNIQUE, o inverso KEY (b,a)também UNIQUEnão importa o quê.
tfrommen
1
@tf Acho que depende do otimizador de consultas. Como você ressalta, é necessário apenas verificar o caminho inverso para que o plano de inserção possa fazer isso de qualquer maneira. A questão está marcada com MySQL - não faço ideia de como isso se comporta.
Martin Smith
Eu sei que essa é uma resposta antiga, mas eu só quero apontar para alguém que tropeça nisso que o MySQL ignora completamente as restrições CHECK (embora as "analise" com êxito), portanto essa abordagem provavelmente não é o caminho a seguir com essa tecnologia.
Micah
@Micah true. Eu não estava ciente de que em 2012. Ainda vai trabalhar em outros SGBDs ...
Martin Smith
+1 para implementar uma visualização para isso. Tendo AB & BA armazenados traz em inconsistência (se a relação não é bidirecional), enquanto este método é uma abordagem melhor
imans77
7

Supondo que uma "amizade" seja sempre bidirecional / mútua, eu provavelmente lidaria com algo assim.

CREATE TABLE person (
    person_id int IDENTITY(1,1) PRIMARY KEY,
    ...other columns...
)

CREATE TABLE friendship (
    friendship_id int IDENTITY(1,1) PRIMARY KEY,
    ...other columns, if any...
)

CREATE TABLE person_friendship (
    person_id int NOT NULL,
    friendship_id int NOT NULL
    PRIMARY KEY (person_id, friendship_id)
)

O resultado é que você muda de uma associação muitos para muitos de "pessoa" para "pessoa", para uma associação muitos para muitos de "pessoa" para "amizade". Isso simplificará junções e restrições, mas tem o efeito colateral de permitir mais de duas pessoas em uma única "amizade" (embora talvez a flexibilidade adicional seja uma vantagem potencial).

db2
fonte
Esse é basicamente um padrão de grupo / associação. Idéia interessante, no entanto.
einSelbst
4

Pode ser necessário definir índices em torno de amizades, em vez de dobrar o número de linhas:

CREATE TABLE person
(
    person_id INT NOT NULL AUTO_INCREMENT,
    ...
    PRIMARY KEY (person_id)
);
CREATE TABLE friendship
(
    friend_of INT NOT NULL,
    friend_to INT NOT NULL,
    PRIMARY KEY (friend_of,friend_to),
    UNIQUE KEY friend_to (friend_to,friend_of)
);

Dessa forma, você duplica o armazenamento para índices, mas não para os dados da tabela. Como resultado, isso deve economizar 25% no espaço em disco. O MySQL Query Optimizer escolherá executar apenas varreduras de intervalo de índice, razão pela qual o conceito de cobertura de índices funciona bem aqui.

Aqui estão alguns links interessantes sobre Cobertura de índices:

EMBARGO

Se a amizade não é mútua, você tem a base para outro tipo de relacionamento: SEGUINTE

Se friend_to não for amigo de friend_of, você pode simplesmente deixar esse relacionamento fora da tabela.

Se você deseja definir relacionamentos para todos os tipos, sejam eles mútuos ou não, provavelmente você pode usar o seguinte layout de tabela:

CREATE TABLE person
(
    person_id INT NOT NULL AUTO_INCREMENT,
    ...
    PRIMARY KEY (person_id)
);
CREATE TABLE relationship
(
    rel_id INT NOT NULL AUTO_INCREMENT,
    person_id1 INT NOT NULL,
    person_id2 INT NOT NULL,
    reltype_id TINYINT,
    PRIMARY KEY (rel_id),
    UNIQUE KEY outer_affinity (reltype_id,person_id1,person_id2),
    UNIQUE KEY inner_affinity (reltype_id,person_id2,person_id1),
    KEY has_relationship_to (person1_id,reltype_id),
    KEY has_relationship_by (person2_id,reltype_id)
);
CREATE TABLE relation
(
    reltype_id TINYINT NOT NULL AUTO_INCREMENT,
    rel_name VARCHAR(20),
    PRIMARY KEY (reltype_id),
    UNIQUE KEY (rel_name)
);
INSERT INTO relation (relation_name) VALUES
('friend'),('follower'),('foe'),
('forgotabout'),('forsaken'),('fixed');

Na tabela de relações, você pode organizar os relacionamentos para incluir o seguinte:

  • Amigos devem ser mútuos
  • Os inimigos podem ser mútuos ou não
  • Os seguidores podem ser mútuos ou não
  • Os outros relacionamentos estariam sujeitos à interpretação (pelo esquecido ou abandonado ou pelo destinatário de vingança (fixo))
  • Possíveis relacionamentos podem ser ampliados

Isso deve ser mais robusto para todos os relacionamentos, seja o relacionamento mútuo ou não.

RolandoMySQLDBA
fonte
oi @rolandomysqldba, sou um grande fã de suas respostas. é realmente útil para mim (neste caso, 1º exemplo). Agora, aqui está uma ressalva para mim, quero um relacionamento único. (por exemplo, se o usuário A é amigo de B, então B amigo de A não é aceitável.) devo fazer com o gatilho? e o desempenho? porque eu tenho uma tabela muito grande (cerca de 1 milhão de registros), e se eu estiver procurando por amigos do usuário A (A é armazenado nos dois campos (friend_of, friend_to) e mysql usando apenas um índice, o desempenho está muito lento. I deve ter para armazenar entradas duplicadas na minha mesa (eg.A-> B, B-> a) Qualquer opção melhor.?
Manish Sapkal
1

Se você pode controlar dentro do aplicativo que o ID de A é sempre menor que o ID de B (pré-ordena os IDs dos elementos A, B), você pode pedir sem um OR (selecione onde id_A = a AND id_B = b, em vez de perguntar (id_A = a AND id_B = b) OR (id_A = b AND id_B = a)), e também mantenha metade dos registros necessários com as aproximações dos outros. Em seguida, você deve usar outro campo para manter o estado do relacionamento (são-amigos, a-solicitado-a-b, b-solicitado-a-a, ex-amigos-a, ex-amigos-b) e pronto.

Esta é a maneira como eu gerenciei meu sistema de amizade, e isso simplifica o sistema e usa metade das linhas que você precisará com outros sistemas, apenas dizendo que A é igual ao menor valor de ID no código.

appartisan
fonte