我应该如何设计友谊表?


33

如果A是的朋友B,那么我应该同时存储值ABBA,还是一个就足够了?两种方法的优缺点是什么?

这是我的观察:

  • 如果同时保留两者,则在收到朋友的请求时必须同时更新两者。
  • 如果我没有同时保留两者,那么我发现在JOIN对该表进行多次处理时很难。

目前,我以一种方式保持这种关系。

在此处输入图片说明

那么在这种情况下我该怎么办?有什么建议吗?


您是否致力于平台,或者这是理论问题?
Nick Chammas 2012年

混合方法又如何:分别在单独的表中模型返回的友谊和未返回的友谊,请确保将友谊正确地插入其中的一个表中,这对于使用当今的SQL产品不是一件好事:(
一天,2012年

@onedaywhen-是的,听起来更适合图形数据库
Nick Chammas'1

@NickChammas:这不是理论问题。我正在研究mysql存储在亚马逊云中的内容。
2012年

1
@Chan-啊,这意味着您不能使用检查约束来强制关系仅以一种方式存储(MySQL不会强制执行这些约束)
Martin Smith

Answers:


30

我将存储AB和BA。友谊实际上是一种双向关系,每个实体相互联系。即使从直觉上讲,我们将“友谊”视为两个人之间的一个链接,但从关系的角度来看,它更像是“ A有一个朋友B”和“ B有一个朋友A”。两个关系,两个记录。


3
非常感谢。我真的需要仔细考虑您的想法!我之所以避免存储AB和BA是因为有存储空间,因为每次结识朋友后,我的表都会存储两倍的存储空间。
2012年

1
您对存储的看法是正确的,但是请记住,如果将其存储为整数,则每个亲朋好友关系将花费大约30个字节(2个记录x 3列x每个整数4个字节= 24个字节加上一些填充)。拥有10个朋友的100万用户仍然只有大约300MB的数据。
datagod 2012年

1
datagod:是的!
2012年

这也是我设计桌子AB和BA的方式。
kabuto178

2
另外,在只有AB而没有BA的情况下,这可能表示“待处理的好友请求”。
格雷格

13

如果友谊是对称的(即不可能AB对方成为朋友,反之亦然),那么我将使用检查约束存储单向关系,以确保每种关系只能以一种方式表示。

另外,我将放弃代理ID并改为使用复合PK(并且可能在反向列上也使用复合唯一索引)。

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)
  ) 

您不必说很难做到的查询,但是您始终可以创建一个视图

CREATE VIEW Foo
AS
SELECT UserID1,UserID2 
FROM Friends
UNION ALL
SELECT UserID2,UserID1 
FROM Friends

我知道这已经很老了,非常抱歉。为了不给s 带来不必要和多余的额外负担,不定义反向友谊索引 不是更好吗?既然我们已经和因为PK是,反转也不管。UNIQUEINSERTPRIMARY KEY (a,b)UNIQUEKEY (b,a)UNIQUE
tfrommen

1
@tf猜猜这取决于查询优化器。正如您所指出的,只需要单向检查一下,这样插入计划就可以执行此操作。这个问题被标记为MySQL-不知道它的行为。
马丁·史密斯

我知道这是一个古老的答案,但是我只想指出一个绊脚石,那就是MySQL完全忽略了CHECK约束(尽管它将成功地“解析”它们),所以这种方法可能不是该技术所采用的方法。
米卡

@Micah是的。我在2012年没有意识到。仍然可以在其他DBMS中使用...
Martin Smith

+1用于实现该视图。存储AB和BA会导致不一致(如果关系不是双向的),而此方法是更好的方法
imans77

7

假设“友谊”始终是双向的,那么我可能会这样处理。

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)
)

结果是您将其从“人”到“人”的多对多连接更改为从“人”到“友谊”的多对多连接。这将简化联接和约束,但是具有一个副作用,即允许在一个“友谊”中允许两个以上的人在一起(尽管可能会有更多的灵活性)。


这基本上是一个组/成员模式。有趣的想法。
einSelbst 2014年

4

您可能需要围绕友谊定义索引,而不是将行数加倍:

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)
);

这样,您就将索引的存储空间增加了一倍,而不是表数据的存储空间增加了一倍。结果,这将节省25%的磁盘空间。MySQL Query Optimizer将只选择执行索引范围扫描,这就是为什么在这里覆盖索引的概念很好用的原因。

以下是涵盖索引上的一些不错的链接:

警告

如果友谊不是相互的,那么您就有建立另一种关系的基础:跟随

如果friend_to不是friend_of的朋友,您可以简单地将这种关系排除在表外。

如果要为所有类型定义关系,无论它们是相互的还是不相互的,都可以使用以下表布局:

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');

在关系表中,您可以安排关系以包括以下内容:

  • 朋友应该是共同的
  • 敌人可能是相互的
  • 追随者可能是共同的,也可能不是
  • 其他关系将受到解释(由被遗忘或被抛弃的人或报仇的接受者(固定))
  • 可能的关系可以进一步扩展

对于所有关系,无论该关系是相互的还是不相互的,这都应该更可靠。


嗨@rolandomysqldba,我非常支持您的答案。它对我真的很有帮助(在本例中为第一个示例)。现在对我来说是一个警告,我想要独特的关系。(例如,如果用户A与B成为朋友,则B与A成为朋友是不可接受的。)我应该使用触发器吗?那性能呢?因为我有一个非常大的表(约100万条记录),并且如果我搜索用户A的朋友(A存储在两个(friend_of,friend_to)字段中,并且mysql仅使用一个索引,那么它的执行速度很慢。我必须在表中存储重复的条目(例如A-> B,B-> A),还有更好的选择吗
Manish Sapkal 2014年

1

如果您可以在应用程序中控制A的ID始终小于B的ID(对A,B元素ID进行预排序),则可以利用无OR的询问(选择id_A = a AND id_B = b的地方)来代替询问(id_A = a AND id_B = b)或(id_A = b AND id_B = a)),并使用另一方的近似值维护所需记录的一半。然后,您应该使用另一个字段来维护关系的状态(are-friends,a-to-to-b,b-soto-to-a,exfriends-a,exfriends-b),然后就完成了。

这就是我管理友谊系统的方式,这简化了系统,并使用了其他系统所需的一半行,只说A等于代码中的较低id值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.