【问题标题】:How to make SQL many-to-many same-type relationship table如何制作SQL多对多同类型关系表
【发布时间】:2013-09-04 00:06:05
【问题描述】:

我是 SQL 的新手,我首先要尝试尽可能多地学习,因为我正在编写代码,这很困难,因为我正在设计我必须使用的数据库虽然所以我想确保我做对了。我学习了多对多桥接表的基础知识,但是如果这两个字段是相同的类型怎么办?假设一个拥有数千名用户的社交网络,您将如何创建一个表格来跟踪谁是谁的朋友?如果有关于每个关系的附加数据怎么办,比如......“约会朋友”。知道会有诸如“显示在 dateY 和 dateZ 之间的 userX 的所有朋友”之类的查询。我的数据库会有这样的几种情况,我想不出一种有效的方法来做到这一点。由于对我来说很多,我认为其他人已经找到了设计表格的最佳方法,对吧?

【问题讨论】:

  • 我不明白你的意思,“如果两个字段是相同的类型怎么办?”您仍然可以拥有一个连接表并向其添加其他属性。
  • 我的意思是,当我在用户和乐队的上下文中学习多对多时,用户可以“喜欢”乐队。所以我被告知要制作一个用户-乐队桥接表,其中一列是用户,另一列是“最喜欢的乐队”。两种不同类型的数据。在这种“相同类型”的方法中,两个列都是相同的类型(“用户”)......所以......我必须将每个关系包含两次,并将关系的所有元数据包含两次。这有意义吗?
  • 在考虑多对多关系时,对象(或行)的“类型”并不是一个重要的考虑因素。这是考虑它的最佳方式:您有两个表,用户和组。一个用户可能属于许多组(一对多)......但一个组也可能由许多用户组成(也是一对多)。因此,多对多关系可以被认为是双向的一对多关系。桥接表是为您调解这种关系的结构。用户不是组,但即使类型不同,它们仍然可能具有这种关系。
  • 基本上他是在问你应该如何为对称的多对多关系建模。
  • 查看 dba stackexchange 中的此线程:dba.stackexchange.com/questions/10199/… 还可以查看 9Lessons 中的示例课程以获得更多想法:9lessons.info/2010/04/database-design-create-tables-and.html

标签: sql


【解决方案1】:

创建一个User 表,然后创建一个Relationships 表,在其中存储两个朋友的id 以及有关他们关系的任何类型的信息。

SQL图

MySQL 代码

CREATE TABLE `Users` (
  `id` TINYINT NOT NULL AUTO_INCREMENT DEFAULT NULL,
  PRIMARY KEY (`id`)
);

CREATE TABLE `Relationships` (
  `id` TINYINT NOT NULL AUTO_INCREMENT DEFAULT NULL,
  `userid` TINYINT NULL DEFAULT NULL,
  `friendid` TINYINT NULL DEFAULT NULL,
  `friended` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
);

ALTER TABLE `Relationships` ADD FOREIGN KEY (userid) REFERENCES `Users` (`id`);
ALTER TABLE `Relationships` ADD FOREIGN KEY (friendid) REFERENCES `Users` (`id`);

SQL 选择

用数据填满表格后,您可以创建 SQL SELECT 查询来获取所有朋友。您的朋友是那些id 在一侧而您的 id 在另一侧的人。您检查双方的id,因此您不需要存储关系两次。此外,您必须排除您的id,因为您不能成为自己的朋友(在正常、健康的世界中)。

SELECT *
FROM Users u
   INNER JOIN Relationships r ON u.id = r.userid
   INNER JOIN Relationships r ON u.id = r.friendid
WHERE
   (r.userid = $myid OR r.friendid = $myid)
   AND r.friended >= $startdate
   AND r.friended <= $enddate
   AND u.id != $myid;

其中$myid$startdate$enddate 可以是PHP 变量,因此您可以用双引号将此代码直接传递到您的数据库。

【讨论】:

  • 所以您是说将每个 userA/userB 对存储两次,一次在 userid 列中使用 userA,一次在friendid 列中使用 userA?然后在friended列中也存储两次信息?
  • 扩展了 SQL 查询。现在你不需要存储重复了。
  • 这是我见过的第一个可能会这样做的答案,尽管我对所有查询仍然有些陌生,所以我不能 100% 确定我是否遵循该 SELECT。您是否从“Users u”中进行选择(我相信这会使 u 成为 Users 的别名?)所以结果将是 Users 表中的整行用户数据?如果我只想要 $myid 的好友列表和他们的friendDate,SELECT 查询甚至不必提及 Users 表,对吧?
  • "其中 $myid、$startdate 和 $enddate 可以是 PHP 变量" - 注意 SQL 注入,要么对变量进行一些严格的清理/转义,要么最好使用诸如 PDO 或MySQLi(在 PHP 中)为您执行此操作。欲了解更多信息,请参阅:stackoverflow.com/questions/60174/…
【解决方案2】:

模型

可以选择跨越三个表的模型。您将有一个明显的表格user,其中包含所有用户的详细信息(姓名、出生日期......)。

CREATE TABLE `user` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45),
  `dob` DATE,
  PRIMARY KEY (`id`)
);

其次,用户的表connection 可以包含授予连接的权限(此连接可以查看我的相册),重要的是,将引用表friendship。我们需要在两者之间使用表格connection,因为一个用户可以连接到许多友谊。

CREATE TABLE `connection` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `user_id` INT NOT NULL,
  `friendship_id` INT NOT NULL,
  `privilege_mask` TINYINT,
  PRIMARY KEY (`id`)
);

friendship 反过来可以包含共享的详细信息,例如建立这种友谊的时间。 连接到相同的用户 friendship 是朋友

CREATE TABLE `friendship` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `met_first_time` DATE,
  PRIMARY KEY (`id`)
);

与迄今为止发布的其他解决方案相比,这种方式将是一个更现实的模型,因为它避免了方向性(不互惠的友谊 - 不应该存在!),但它会做更多的工作实施。

查询好友

一个查询你朋友名字的例子可能是(虽然没有测试):

SELECT B.name FROM user A
  INNER JOIN connection conn_A ON conn_A.user_id = A.id
    INNER JOIN connection conn_B ON conn_A.friendship_id = conn_B.friendship_id
    INNER JOIN user B ON conn_B.user_id = B.id
    INNER JOIN friendship ON friendship.id = conn_A.friendship_id
  WHERE A.name = 'Dan' AND A.id <> B.id AND
    friendship.met_first_time BETWEEN '2013-4-1' AND '2013-6-30';

您可能会注意到,如果您不关心交朋友的日期,则无需将JOIN 发送到friendship 表,因为连接已经共享friendship_id 密钥。任何此类查询的本质都是conn_Aconn_B 之间的JOIN conn_A.friendship_id = conn_B.friendship_id

【讨论】:

  • 也许我选择了用户/友谊的坏例子,因为人们有一个先入之见,即它涉及发送需要被接受的朋友请求。 userA->userB 或 userB->userA 之间没有区别。有没有友谊关系。但如果有的话,还有关于它的元数据(日期等)。我给出的另一个类似数据结构的例子是邮政编码。每个邮政编码都与其他邮政编码有一段距离。 zipA->zipB=zipB->zipA。所有这些距离应该如何存储而不重复?
  • 完全正确:我的观点是友谊没有方向性。这就是为什么我认为我概述的模型适合您。两个user 条目可以共享相同的friendship,即每个都有一个connection 来表示该友谊。共享元数据(您的示例:日期)将在表 friendship 中,“定向”性质的元数据(例如,从一个用户授予另一用户的权限)将在表 connection 中。
  • 好的,我现在了解你的模型了。我只关心可视化我必须编写什么样的选择查询,作为一个需要大量试验和错误的初学者。我知道的足够多,知道这是可能的。 :-) 那么在您的模型中,SELECT 将用于查找在 dateY 和 dateZ 之间相遇的 userX 的所有朋友?
  • 刚刚添加了一个示例查询,用于查找您在 4 月初至 6 月底之间结交的朋友。
  • 谢谢 :) 虽然 SELECT 查询确实让我质疑我对学习 SQL 的兴趣:-p
【解决方案3】:

一个链接表就足够了,像这样:

People( PersonId bigint, Name nvarchar, etc )
Friends( FromPersonId bigint, ToPersonId bigint, DateAdded datetime )

查询示例:

谁是我的朋友? (即加我为好友但不一定回报的人)

SELECT
    People.Name
FROM
    Friends
    INNER JOIN People ON Friends.FromPersonId = People.PersonId
WHERE
    Friends.ToPersonId = @myPersonId

谁在两个日期之间加了我?

SELECT
    People.Name
FROM
    Friends
    INNER JOIN People ON Friends.FromPersonId = People.PersonId
WHERE
    Friends.ToPersonId = @myPersonId
    AND
    Friends.DateAdded >= @startDate
    AND
    Friends.DateAdded <= @endDate

【讨论】:

  • 我想我不清楚。用户(或我正在使用的其他类似表)将没有“从”和“到”。也许将其想象为邮政编码而不是用户。每个邮政编码都有一个与其他邮政编码相关联的距离值。不管你是 zipA->zipB,还是 zipB->zipA。这是相同的值。这是否更好地解释了我想要设计的那种数据结构?
  • 您提议的内容(一个包含彼此距离的邮政编码表)是一个不正确的设计,因为它将使用n^2 空间(即一个包含 10 个邮政编码的表将需要 100 个距离行, 100 个邮政编码将需要 10,000 个距离,依此类推)。相反,如果您的 DBMS 支持,请考虑使用地理空间类型。 MS-SQL 和 MySQL 都支持地理空间查询。
  • 地理空间查询没有考虑到在两个位置之间有一个大湖,有人必须开车绕行,所以这是行不通的。我将道路距离作为数据,并且不打算存储每场比赛,只是最近的一场比赛,但这与我最初的问题相去甚远。
【解决方案4】:

您应该使用两个用户的 ID 作为关系表中的 PRIMARY KEY(即使不是双向的关系也是唯一的)。类似的东西

创建表Users (id int(9) NOT NULL AUTO_INCREMENT,
主键 (id) );

创建表Relationships (id1 int(9) NOT NULL, id2 int(9) 非空,friended 时间戳非空,主键 (id1, id2));

请注意:

  • id1和id2参考Users表(上面的代码非常简化)
  • 即使关系不是“双向”的,你也可以想 如果您有 id1 - id2 似乎 id1 用户将 id2 用户添加为 朋友,但不一定相反 - THEN id2 用户可以添加 id1 用户作为朋友 - 在您可以拥有的关系表中 这些可能的组合:
    • id1-id2,(只有1加2为好友)
    • id2-id1,(仅2加1为好友)
    • id1-id2 和 id2-id1(两者都 - 这意味着关系表中的 2 行,并且两者都是相互的 朋友)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-15
    • 2017-01-30
    • 1970-01-01
    • 2023-02-26
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 2010-11-21
    相关资源
    最近更新 更多