为多种用户类型及其联系信息建模数据库结构


10

我正在设计一个数据库,该数据库将存储各种类型的用户。他们主要(但不是唯一)是演员,导演和作家。当前只有四种相关的用户类型。这个数字可能会有所增加,但是可能性很小-在这种情况下,这个数字很小。

该计划是要有一个users表,几乎只负责登录该网站(nameemail以及一password列和另外两列,例如它们是否已被批准,以及updated_at),以及每个用户类型的附加表有自己独特的一组列。例如,只有演员才会有种族栏,只有导演会有传记栏,只有作家才需要提供位置。但是,由于我以前没有管理过这种复杂的数据库,所以我想知道如何组织几个方面:

首先,用户可以是以上类型中的任何一种,也可以是任何组合。因此,我了解到我需要director_user带有director_iduser_id列的表(例如)。这样是否足以按角色类型过滤所有用户?

其次,大多数用户会选择Twitter个人资料和电话号码。并且所有演员必须为其其他任何在线演员个人资料至少包含一个网址;目前可以包含三个,但是这个数目可能会增加。我是否认为每种可能的个人资料/联系方式的单独表格是组织数据的最佳方式,对吗?

Answers:


14

根据我对您感兴趣的业务上下文的描述的解释,您正在处理超类型1型结构,其中(a)演员导演作家是(b)人员,其实体超类型和(c)的实体子类型。所述子类型不是互斥的。

这样,如果您有兴趣建立一个可以准确反映这种情况的关系数据库,并因此希望它能正常运行,那么您对以下几点的澄清对于前面的观点是非常重要的,因为它们在以下方面具有一定的意义。 (1)有关数据库的概念表示和(2)逻辑表示形式:

  • […]每个用户类型的附加表,每个表都有自己独特的列集。

  • […]只有四种相关的用户类型。这个数字可能会有所增加,但是可能性很小-在这种情况下,这个数字很小。

我将在以下各节中详细介绍所有这些方面以及其他一些关键因素。

商业规则

为了首先定义相应的概念架构(可以用作后续参考,以便您可以对其进行调整以确保它满足确切的信息要求),我制定了一些特别重要的业务规则:

  • 一个可以扮演一到两个或三个(即一对一)的角色2。换句话说,一个可能是
    • 一个演员
    • 一个导演
    • 一个作家
  • 一个可以通过零或一个UserProfile登录。
  • 一个演员提供一,二,或三网址3
  • 一个演员是一个分组种族
  • 一个种族群体零一或一对多的演员
  • 一个作家是基于在一个位置
  • 位置是零一个或更多的的基部作家

信息库IDEF1X图表

然后,我创建了图1所示的IDEF1X 4图,该将上面的所有公式以及其他相关的规则归为一组:

图1-Cinema中人物角色和联系方式的IDEF1X图

如图所示,Person超类型(i)有其自己的框,(ii)具有适用于所有子类型的属性或属性,并且(iii)给出了将其与每个子类型的框连接的线。

反过来,每个子类型(a)出现在其自己的专用框中,并且(b)仅保留其适用的属性。类型PersonId的主键将5迁移到角色名称分别为6 ActorIdDirectorIdWriterId的子类型。

另外,我避免将PersonUserProfile实体类型耦合在一起,这允许将它们的所有上下文含义,关联或关系等分开。PersonId属性已迁移到角色名称为UserId的UserProfile

您在问题正文中指出

并且所有演员必须为其其他任何在线演员个人资料至少包含一个网址;目前可以包含三个,但是这个数目可能会增加。

…因此,URL本身就是一种实体类型,并且根据此引用直接与Actor子类型相关联。

并且,在注释中,您可以指定

[…]演员将有头像(照片​​),而作家将没有[…]

…然后,在其他功能中,我将Headshot包含为Actor实体类型的属性。

至于种族地点实体类型,它们当然可能需要更复杂的组织(例如,演员可能以不同的比例属于一个,两个或多个不同的种族,作家可能基于需要记录的地方国家/地区,行政区域,县等),但您的业务环境需求似乎已通过此处建模的结构成功满足。

当然,您可以根据需要进行尽可能多的调整。

说明性的SQL-DDL逻辑设计

因此,基于上面显示和描述的IDEF1X图表,我编写了如下所示的逻辑DDL布局(我提供了注释作为注释,解释了我认为对于表,列和约束特别重要的某些特征。声明):

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

-- Also, you should make accurate tests to define the 
-- most convenient INDEX strategies based on the exact 
-- data manipulation tendencies of your business needs.

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

CREATE TABLE Person ( -- Represents the supertype.
    PersonId       INT      NOT NULL,
    FirstName      CHAR(30) NOT NULL,
    LastName       CHAR(30) NOT NULL,
    BirthDate      DATE     NOT NULL,
    GenderCode     CHAR(3)  NOT NULL,
    TwitterProfile CHAR(30) NOT NULL,
    PhoneNumber    CHAR(30) NOT NULL,
    EmailAddress   CHAR(30) NOT NULL,  
    CreatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT Person_PK  PRIMARY KEY (PersonId),
    CONSTRAINT Person_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
        FirstName,
        LastName,
        GenderCode,
        BirthDate
    ),
    CONSTRAINT Person_AK2 UNIQUE (TwitterProfile), -- ALTERNATE KEY.
    CONSTRAINT Person_AK3 UNIQUE (EmailAddress)    -- ALTERNATE KEY.
);

CREATE TABLE Ethnicity ( -- Its rows will serve a “look-up” purpose.
    EthnicityId     INT      NOT NULL,
    Name            CHAR(30) NOT NULL,  
    Description     CHAR(30) NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    -- 
    CONSTRAINT Ethnicity_PK PRIMARY KEY (EthnicityId),
    CONSTRAINT Ethnicity_AK UNIQUE      (Description)   
);

CREATE TABLE Actor ( -- Stands for one of the subtypes.
    ActorId         INT      NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    Headshot        CHAR(30) NOT NULL, -- May, e.g., contain a URL indicating the path where the photo file is actually stored. 
    EthnicityId     INT      NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    -- 
    CONSTRAINT Actor_PK            PRIMARY KEY (ActorId),
    CONSTRAINT ActorToPerson_PK    FOREIGN KEY (ActorId)
        REFERENCES Person (PersonId),
    CONSTRAINT ActorToEthnicity_PK FOREIGN KEY (EthnicityId)
        REFERENCES Ethnicity (EthnicityId)   
);

CREATE TABLE Director ( -- Denotes one of the subtypes
    DirectorId      INT       NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    Bio             CHAR(120) NOT NULL,  
    Etcetera        CHAR(30)  NOT NULL,
    CreatedDateTime DATETIME  NOT NULL,
    -- 
    CONSTRAINT Director_PK         PRIMARY KEY (DirectorId),
    CONSTRAINT DirectorToPerson_PK FOREIGN KEY (DirectorId)
        REFERENCES Person (PersonId)   
);

CREATE TABLE Country (
    CountryCode     CHAR(2)  NOT NULL,
    Name            CHAR(30) NOT NULL,  
    CreatedDateTime DATETIME NOT NULL,
    -- 
    CONSTRAINT Country_PK PRIMARY KEY (CountryCode),
    CONSTRAINT Country_AK UNIQUE      (Name)   
);

CREATE TABLE Location ( -- Its rows will serve a “look-up” purpose.
    CountryCode     CHAR(2)  NOT NULL,
    LocationCode    CHAR(3)  NOT NULL,
    Name            CHAR(30) NOT NULL,  
    CreatedDateTime DATETIME NOT NULL,
    -- 
    CONSTRAINT Location_PK PRIMARY KEY (CountryCode, LocationCode),
    CONSTRAINT Location_AK UNIQUE      (CountryCode, Name)   
);

CREATE TABLE Writer ( -- Represents one of the subtypes.
    WriterId        INT      NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    CountryCode     CHAR(2)  NOT NULL,
    LocationCode    CHAR(3)  NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    -- 
    CONSTRAINT Writer_PK           PRIMARY KEY (WriterId),
    CONSTRAINT WriterToPerson_PK   FOREIGN KEY (WriterId)
        REFERENCES Person (PersonId),
    CONSTRAINT WriterToLocation_PK FOREIGN KEY (CountryCode, LocationCode)
        REFERENCES Location (CountryCode, LocationCode)  
);

CREATE TABLE UserProfile (
    UserId          INT      NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    UserName        CHAR(30) NOT NULL,
    Etcetera        CHAR(30) NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    -- 
    CONSTRAINT UserProfile_PK PRIMARY KEY (UserId),
    CONSTRAINT UserProfile_AK UNIQUE      (UserName), -- ALTERNATE KEY.
    CONSTRAINT UserProfileToPerson_PK FOREIGN KEY (UserId)
        REFERENCES Person (PersonId)    
);

CREATE TABLE URL (
    ActorId       INT      NOT NULL,
    Address       CHAR(90) NOT NULL,
    Etcetera      CHAR(30) NOT NULL,
    AddedDateTime DATETIME NOT NULL,
    -- 
    CONSTRAINT URL_PK        PRIMARY KEY (ActorId, Address), -- Composite PRIMARY KEY.
    CONSTRAINT URLtoActor_FK FOREIGN KEY (ActorId)
        REFERENCES Actor (ActorId)
);

因此,(1)逻辑布局的每一个奇异方面所携带来自一个非常精确的含义(2)感兴趣的商业环境的奇异特性7与精神-in协议关系框架埃德加·弗兰克·科德博士 - ,因为:

  • 每个基本表代表一个单独的实体类型。
  • 代表相应实体类型的单个属性。
  • 为确保每一固定一个特定的数据类型,以确保它包含的所有都属于一个特定且正确定界的集合a,它可以是INT,DATETIME,CHAR等(并且让我们希望MySQL最终将合并DOMAIN支持近期版本)。
  • 配置(声明性)多个约束,以确保保留在所有表中的形式的断言符合在概念级别确定的业务规则。
  • 旨在传达明确定义的语义,例如,Person读取一行

    由PersonId标识的Person由rFirstName s和LastName 调用t,出生于BirthDate u,具有GenderCode v,TwitterProfile上的推文w,可通过PhoneNumber进行访问x,可通过EmailAddress进行联系y并在CreatedDateTime上注册z

具有这样的布局绝对是有利的,因为您可以派生新表(例如,借助JOIN子句从多个表中收集列的SELECT操作),这些表也具有非常精确的含义(请参见本节)。标题为“视图”)。

值得一提的是,通过这种配置,(i)表示子类型实例的行由(ii)相同的PRIMARY KEY值标识,该值区分表示互补超型出现的行。因此,有更多的机会注意到

  • (a)在(b)代表亚型的表上附加一个额外的列来保存系统生成的和系统分配的替代项8(c)完全多余

通过这种逻辑设计,如果在业务上下文中将新的子类型定义为相关的,则必须声明一个新的基表,但是当其他类型的实体类型被认为具有重要意义时,也会发生这种情况,因此,其实很普通。

观看次数

为了“获取”例如与ActorDirectorWriter对应的所有信息,您可以声明一些视图(即派生表或可表达表),以便您可以直接从一个资源中进行选择,而不必编写每次都涉及JOIN;例如,使用下面声明的VIEW,您可以获得“完整”的Actor信息:

--
CREATE VIEW FullActor AS

    SELECT P.FirstName,
           P.Lastname,
           P.BirthDate,
           P.GenderCode,
           P.TwitterProfile,
           P.PhoneNumber,
           P.EmailAddress,
           A.Headshot,
           E.Name AS Ethnicity
         FROM Person P
         JOIN Actor A
           ON A.ActorId     = P.PersonId
         JOIN Ethnicity E
           ON E.EthnicityId = A.EthnicityId;
--

当然,您可以采用类似的方法来检索“完整”的DirectorWriter信息:

--
CREATE VIEW FullDirector AS

    SELECT P.FirstName,
           P.Lastname,
           P.BirthDate,
           P.GenderCode,
           P.TwitterProfile,
           P.PhoneNumber,
           P.EmailAddress,
           D.Bio,
           D.Etcetera
         FROM Person P
         JOIN Director D
           ON D.DirectorId = P.PersonId; 

--
CREATE VIEW FullWriter AS

    SELECT P.FirstName,
           P.Lastname,
           P.BirthDate,
           P.GenderCode,
           P.TwitterProfile,
           P.PhoneNumber,
           P.EmailAddress,
           L.Name AS Location,
           C.Name AS Country
         FROM Person P
         JOIN Writer W
           ON W.WriterId     = P.PersonId
         JOIN Country C
           ON C.CountryCode  = W.CountryCode
         JOIN Location L
           ON L.LocationCode = W.LocationCode;   
--

我已经发布了在MySQL 5.6上运行的此SQL Fiddle中讨论的所有DDL语句和DML视图,以便您可以“实际”查看和测试它们。


尾注

1在某些概念建模技术中,超类型-子类型关联称为超类-子类关系。

2尽管您提到事实上一个可能会执行更多的角色,但是您所揭示的三个角色足以讨论暴露了几个重要影响的场景。

3但是,正如您指出的那样,将来Actor最终可能会提供一对多URL

4 信息建模的集成定义IDEF1X)是一种高度推荐的建模技术,由美国国家标准技术研究院(NIST)于1993年12月建立为标准。它基于(a)数据关系模型的唯一发起人,即EF Codd博士创作的早期理论著作;(b)由陈PP博士提出的关于实体关系的观点;以及(c)Robert G. Brown创建的逻辑数据库设计技术。

5 IDEF1X标准将密钥迁移定义为“将父代或通用[即,超类型]实体的主键放在其子代或类别实体[即,子类型]中作为外键的建模过程”。

6在IDEF1X中,角色名称是分配给FK属性的独特标签,目的是表达角色名称在其各自实体类型范围内的含义。

7当然,对于假设的概念属性(和逻辑列),Director.EtceteraUserProfile.Etcetera只是占位符,我只是用来展示添加适用于相应概念实体类型的更多属性(和列)的可能性(和逻辑表)。

8例如,将具有AUTO_INCREMENT属性的附加列附加到在MySQL上“运行”的数据库表中。


2

您应该像这样在多个表之间进行拆分(仅显示表示概念所需的列,而不必显示所有列):

Users
ID   Username   FirstName   LastName   PasswordHash ...
 1   'Joe1'      'Joe'      'Smith'
 2   'Freddy'    'Fred'     'Jones'

Roles
ID   RoleType ....
 1   'Writer'
 2   'Director'
 3   'Actor'

User_Roles
User_ID   Role_ID ...
1         1
1         2
2         2
2         3

这将为您提供一个充满用户的表以及所有各种用户列,一个角色表以及一个将两者连接在一起的链接表。

通过User_Roles中的条目可以看到Joe1既是作家又是导演。弗雷迪既是导演又是演员。

这也使您以后可以添加更多角色而无需更改系统。只需插入生产者或编辑者的记录,或任何其他内容。

因此,要查找演员的所有用户名,您有两种选择:

 Select Distinct Username
   from Users
  Where User_ID in (select User_ID from User_Roles where Role_ID = 3)

或者,如果您不知道role_ID号,则:

 Select Distinct Username
   from Users
  Where User_ID in (Select User_ID from User_Roles where Role_ID = 
                       (Select ID from Roles where RoleType = 'Actor')
                   )

或者,您也可以这样做:

select u.Username, r.RoleType
  from Users u
 inner join User_Roles ur on ur.User_ID = u.ID
 inner join Roles r on r.ID = ur.Role_ID
 where r.RoleType = 'Actor'

(在此版本中,您还可以使用Where r.Role_ID = 3来获得相同的结果。)

但是我将使用第一个查询以及我知道的任何WHERE子句。在大型系统中,知道Role_ID通常比运行文本快,因为对于大多数SQL引擎而言,数字数据“更容易”且效率更高。

至于联系方式或图片或其他方式,我会以类似的方式进行:

Attributes
ID    MethodText    ...
1     'TwitterID'
2     'URL'
3     'CellPhone'
4     'Email'
5     'PictureLink'

Role_Attributes
Role_ID  Attribute_ID isRequired
3        5             1
3        4             1
3        3             0

User_Attributes
User_ID  Attribute_ID  AttributeData
1         4            'Joe@Example.com'
1         1            '@joe'
1         3            '555-555-5555'
1         5            'www.example.com/pics/myFace.png'

...等等。这些将以与用户相同的方式链接到角色。

这表明每个角色都有0到许多属性,这可以是可选的。然后,每个用户都有0到许多属性,其中包含这些属性的数据。

这样,您就可以随着时间的流逝添加新属性,而无需重写任何代码。只需更新属性和role_attributes表以匹配您的新规则即可。它还使您可以在角色之间共享属性,而无需为每个用户重新输入相同的数据。如果两个角色需要图片,那么他们只需上传1张图片即可满足要求。


啊哈,我认为这在某种意义上是可行的……但是我不清楚如何(例如)列出所有参与者以及他们的用户名(假设参与者数据存储在其中)。单独的表格)。
verism

查看我的修改;我添加了查询示例。
CaM

这非常有帮助,谢谢。我想我的问题可能还不太清楚-抱歉。我应该更清楚地指出,每种类型(演员,导演等)的表将具有仅与该用户类型相关的自己的唯一属性集。例如,演员有头像(照片​​),而作家没有。再次道歉,因为没有对此更加明确。
verism

接触方法看起来是一个很好的解决方案。
verism

1
将其更改为属性,以满足对照片等的要求。现在应该更合适。
CaM
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.