设计友谊数据库结构:我应该使用多值列吗?

yel*_*eln 13 normalization foreign-key database-design relational-theory

假设我有一个名为 的表User_FriendList,它具有以下特征:

CREATE TABLE User_FriendList (
    ID ...,
    User_ID...,
    FriendList_IDs...,
    CONSTRAINT User_Friendlist_PK PRIMARY KEY (ID)
);
Run Code Online (Sandbox Code Playgroud)

让我们假设该表包含以下数据:

 +----+---------+---------------------------+
 | 身份证| 用户 ID | 好友            列表_ID |
 +----+---------+---------------------------+
 | 1 | 102 | 2:15:66:35:26:17: |
 +----+---------+---------------------------+
 | 2 | 114 | 1:12:63:33:24:16:102 |
 +----+---------+---------------------------+
 | 3 | 117 | 6:24:52:61:23:90:97:118 |
 +----+---------+---------------------------+

注:该“:”当(冒号)是分隔符爆炸在PHP成array

问题

所以:

  • 这是一个方便的方式来“存储”的IDsFriendList

  • 或者,相反,我是否应该让FriendId每一行只有一个值,并且当我需要检索给定列表的所有行时,只需执行如下查询SELECT * FROM UserFriendList WHERE UserId = 1

MDC*_*CCL 23

管理单个信息

假设,在您的业务领域,

  • 一个用户可以有零个或多个好友
  • 朋友必须首先被登记为用户; 和
  • 您将搜索、和/或添加、和/或删除和/或修改好友列表的单个值;

那么在多值列中收集的每个特定数据Friendlist_IDs代表一个单独的信息,具有非常精确的含义。因此,列

  • 需要一组适当的显式约束,并且
  • 它的值有可能通过几个关系操作(或它们的组合)被单独操纵。

简答

因此,您应该保留Friendlist_IDs(a) 中每行只接受一个唯一值的列中的每个值,在 (b) 表中表示可以发生在用户之间的概念级关联类型,即友谊- 作为我将在以下部分中举例说明——。

通过这种方式,您将能够将 (i) 所述表作为数学关系处理,以及 (ii) 将所述列作为数学关系属性处理——当然,只要 MySQL 及其 SQL 方言允许——。

为什么?

因为数据关系模型,由E.?F.博士创建。Codd,要求表由列组成,每行只保存一个适用类型的值;因此,声明一个表的列可以包含多个域或类型的值 (1) 并不表示数学关系,并且 (2) 将不允许获得上述理论框架中提出的优势。

建模用户之间的友谊:首先定义业务环境规则

我强烈建议开始塑造一个数据库,首先根据相关业务规则的定义来界定相应的概念模式,除其他因素外,这些规则必须描述感兴趣的不同方面之间存在的相互关系类型,即,适用的实体类型及其属性;例如:

  • 一个用户主要是由他或她的识别用户ID
  • 一个用户交替由他或她的组合标识的名字姓氏性别生日
  • 一个用户是通过交替他或她的识别用户名
  • 用户请求者的零一或一对多友谊
  • 用户收件人的零一或一对多友谊
  • 一个Friendship主要由它的RequesterId和它的AddresseeId的组合来标识

说明性IDEF1X图

通过这种方式,我能够推导出图 1所示的 IDEF1X 1图,它集成了之前制定的大部分规则:

图 1. 用户友谊 IDEF1X 图

如图所示,请求者收件人是表示参与给定友谊的特定用户所执行角色的符号

既然如此,Friendship实体类型描绘了一种多对多(M:N) 基数比的关联类型,它可以涉及相同实体类型的不同出现,即User。因此,它是被称为“物料清单”或“零件爆炸”的经典结构的一个例子。


1所 对于信息建模集成定义 IDEF1X)是被确立为一个非常可取的技术标准由美国在1993年12月美国国家标准与技术研究院(NIST)。它完全基于 (a)关系模型的唯一创始人EF Codd 博士撰写的早期理论材料;关于 (b) 数据的实体关系视图,由PP Chen 博士开发;以及 (c) 逻辑数据库设计技术,由 Robert G. Brown 创建。


说明性 SQL-DDL 逻辑设计

然后,从上面展示的 IDEF1X 图中,声明如下所示的 DDL 安排更加“自然”:

-- You should determine which are the most fitting 
-- data types and sizes for all the table columns 
-- depending on your business context characteristics.

-- At the physical level, you should make accurate tests 
-- to define the mostconvenient INDEX strategies based on 
-- the pertinent query tendencies.

-- As one would expect, you are free to make use of 
-- your preferred (or required) naming conventions. 

CREATE TABLE UserProfile ( -- Represents an independent entity type.
    UserId          INT      NOT NULL,
    FirstName       CHAR(30) NOT NULL,
    LastName        CHAR(30) NOT NULL,
    BirthDate       DATE     NOT NULL,
    GenderCode      CHAR(3)  NOT NULL,
    Username        CHAR(20) NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT UserProfile_PK  PRIMARY KEY (UserId),
    CONSTRAINT UserProfile_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
        FirstName,
        LastName,
        GenderCode,
        BirthDate
    ),
    CONSTRAINT UserProfile_AK2 UNIQUE (Username) -- Single-column ALTERNATE KEY.
);

CREATE TABLE Friendship ( -- Stands for an associative entity type.
    RequesterId     INT      NOT NULL,
    AddresseeId     INT      NOT NULL, -- Fixed with a well-delimited data type.
    CreatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT Friendship_PK            PRIMARY KEY (RequesterId, AddresseeId), -- Composite PRIMARY KEY.
    CONSTRAINT FriendshipToRequester_FK FOREIGN KEY (RequesterId)
        REFERENCES UserProfile (UserId),
    CONSTRAINT FriendshipToAddressee_FK FOREIGN KEY (AddresseeId)
        REFERENCES UserProfile (UserId)
);
Run Code Online (Sandbox Code Playgroud)

以这种方式:

  • 每个基表代表一个单独的实体类型;
  • 代表各自实体类型的唯一属性;
  • 每个都有一个特定的数据类型a是固定的,以保证它包含的所有都属于一个特定且定义明确的集合,无论是 INT、DATETIME、CHAR 等;和
  • 多个约束b被配置(声明性地)以确保所有表中保留的形式的断言满足在概念模式中确定的业务规则。

单值列的优点

正如所演示的,您可以,例如:

  • 利用由数据库管理系统(为简洁起见为 DBMS)强制执行的参照完整性Friendship.AddresseeId,因为将其约束为对列进行引用的 FOREIGN KEY(为简洁起见,FK)可UserProfile.UserId确保每个值都指向现有行。

  • 创建由列组合组成的复合PRIMARY KEY (PK) (Friendship.RequesterId, Friendship.AddresseeId),有助于优雅地区分所有 INSERTed 行,并自然地保护它们的唯一性

    当然,这意味着为系统分配的代理值附加一个额外的列(例如,在 Microsoft SQL Server 中使用IDENTITY属性或在 MySQL 中使用AUTO_INCREMENT属性设置的列)和辅助 INDEX 是完全多余的

  • 将保留的值限制Friendship.AddresseeId为精确的数据类型c(它应该匹配,例如,为 建立的类型UserProfile.UserId,在本例中为 INT),让 DBMS 负责相关的自动验证。

    这个因素也可以帮助 (a) 利用相应的内置类型函数和 (b) 优化磁盘空间使用。

  • 通过为列配置小而快速的从属索引来优化物理级别的数据检索,因为这些物理元素可以极大地帮助加快涉及所述列的查询。Friendship.AddresseeId

    当然,您可以,例如,单独放置一个单列 INDEX Friendship.AddresseeId,一个包含Friendship.RequesterIdFriendship.AddresseeId 或两者的多列索引。

  • 避免通过“搜索”收集在同一列中的不同值(很可能重复、错误键入等)而引入的不必要的复杂性,这是一个最终会减慢系统运行速度的操作过程,因为您会必须求助于资源和耗时的非关系方法来完成上述任务。

因此,有多种原因需要仔细分析相关业务环境,以便准确地标记出每个表列的类型d

如前所述,数据库设计者所扮演的角色对于充分利用 (1)关系模型提供的逻辑级优势和 (2)所选 DBMS 提供的物理机制至关重要。


a , b , c , d显然,当使用支持 DOMAIN 创建(一种独特的关系特性)的SQL 平台(例如FirebirdPostgreSQL)时,您可以声明只接受属于它们各自的值的列(适当约束,有时共享)域。


一个或多个应用程序共享所考虑的数据库

当你聘请arrays的应用程序(一个或多个)accesing数据库的代码,你只需要检索相关数据集(S)完全然后“绑定”它(们)的有关代码结构或执行应该发生的相关应用程序进程。

单值列的更多好处:数据库结构扩展更容易

AddresseeId数据点保存在其保留且类型正确的列中的另一个优点是它极大地促进了数据库结构的扩展,我将在下面举例说明。

场景进展:纳入友谊状态概念

由于友谊会随着时间的推移而发展,您可能必须跟踪这种现象,因此您必须 (i) 扩展概念模式和 (ii) 在逻辑布局中声明更多的表。因此,让我们安排下一个业务规则来描述新的公司:

  • 一个Friendship持有一对多的FriendshipStatuses
  • FriendshipStatus主要由其的组合来标识RequesterId,其AddresseeId及其SpecifiedDateTime
  • 用户指定零酮或一对多FriendshipStatuses
  • 一个状态进行分类零一或一对多FriendshipStatuses
  • 一个状态主要由其确定的StatusCode
  • 一个状态交替地通过它的标识名称

扩展的 IDEF1X 图

随后,可以扩展先前的 IDEF1X 图以包括上述新的实体类型和相互关系类型。图 2 显示了描述与新元素相关联的先前元素的图表

图 2. 友谊状态 IDEF1X 图

逻辑结构添加

之后,我们可以使用以下声明来延长 DDL 布局:

--
CREATE TABLE MyStatus ( -- Denotes an independent entity type.
    StatusCode CHAR(1)  NOT NULL,
    Name       CHAR(30) NOT NULL,
    --
    CONSTRAINT MyStatus_PK PRIMARY KEY (StatusCode),
    CONSTRAINT MyStatus_AK UNIQUE      (Name) -- ALTERNATE KEY.
); 

CREATE TABLE FriendshipStatus ( -- Represents an associative entity type.
    RequesterId       INT      NOT NULL,
    AddresseeId       INT      NOT NULL,
    SpecifiedDateTime DATETIME NOT NULL,
    StatusCode        CHAR(1)  NOT NULL,
    SpecifierId       INT      NOT NULL,
    --
    CONSTRAINT FriendshipStatus_PK             PRIMARY KEY (RequesterId, AddresseeId, SpecifiedDateTime), -- Composite PRIMARY KEY.
    CONSTRAINT FriendshipStatusToFriendship_FK FOREIGN KEY (RequesterId, AddresseeId)
        REFERENCES Friendship  (RequesterId, AddresseeId), -- Composite FOREIGN KEY.
    CONSTRAINT FriendshipStatusToMyStatus_FK   FOREIGN KEY (StatusCode)
        REFERENCES MyStatus    (StatusCode),
    CONSTRAINT FriendshipStatusToSpecifier_FK  FOREIGN KEY (SpecifierId)
        REFERENCES UserProfile (UserId)      
);
Run Code Online (Sandbox Code Playgroud)

因此,每次需要更新给定友谊状态时,用户只需插入一个新行,其中包含:FriendshipStatus

  • 合适的RequesterIdAddresseeId值——取自相关Friendship行——;

  • 新的和有意义的StatusCode价值MyStatus.StatusCode——取自——;

  • 确切的 INSERTion 瞬间,即SpecifiedDateTime——最好使用服务器功能,以便您能够以可靠的方式检索和保留它——;和

  • SpecifierId将表明各自的值UserId是进入新FriendshipStatus进入系统-ideally,与您的应用程序的帮助下(一个或多个)提供的设施。

在这种情况下,让我们假设该MyStatus表包含以下数据 - PK 值是 (a) 最终用户、应用程序程序员和 DBA 友好和 (b) 在物理实现级别的字节数方面小而快—:

 +-———————————-+-—————————-+
 | 状态码| 姓名      |
 +-———————————-+-—————————-+
 | R | 要求 |
 +------------+------------+
 | 一个 | 接受 |
 +------------+------------+
 | D | 拒绝 |
 +------------+------------+
 | 乙 | 肿 |
 +------------+------------+

因此,该FriendshipStatus表可能包含如下所示的数据:

 +-————————————-+-————————————-+-————————————————————— ————+-—————————————-+-————————————-+
 | 请求者 ID | 收件人ID | 指定日期时间       | 状态码| 说明符 ID |
 +-————————————-+-————————————-+-————————————————————— ————+-—————————————-+-————————————-+
 | 1750 | 1748 | 2016-04-01 16:58:12.000 | R | 1750 |
 +------------+-------------+------------ ----+------------+------------+
 | 1750 | 1748 | 2016-04-02 09:12:05.000 | 一个 | 1748 |
 +------------+-------------+------------ ----+------------+------------+
 | 1750 | 1748 | 2016-04-04 10:57:01.000 | 乙 | 1750 |
 +------------+-------------+------------ ----+------------+------------+
 | 1750 | 1748 | 2016-04-07 07:33:08.000 | R | 1748 |
 +------------+-------------+------------ ----+------------+------------+
 | 1750 | 1748 | 2016-04-08 12:12:09.000 | 一个 | 1750 |
 +------------+-------------+------------ ----+------------+------------+

如您所见,可以说该FriendshipStatus表用于组成时间序列


相关帖子

您可能还对以下内容感兴趣:

  • 在这个答案中,我提出了一种基本方法来处理两种不同实体类型之间常见的多对多关系。
  • 图 1 中显示的 IDEF1X 图说明了另一个答案。特别注意名为MarriageProgeny的实体类型,因为它们是如何处理“零件爆炸问题”的另外两个示例。
  • 这篇文章简要讨论了如何在单个列中保存不同的信息。