使用属性

时间:2016-01-07 21:04:54

标签: sql database-design

我遇到了一个桌子设计,立刻让我觉得奇怪,但现在我已经想过它,我似乎无法想出一个我真的很高兴的设计。

现有设计(简化)是:

CREATE TABLE Accounts (
    account_id      INT NOT NULL,
    account_name    VARCHAR(50) NOT NULL,
    CONSTRAINT PK_Accounts PRIMARY KEY CLUSTERED (account_id)
)

CREATE TABLE Groups (
    group_id      INT NOT NULL,
    group_name    VARCHAR(50) NOT NULL,
    CONSTRAINT PK_Groups PRIMARY KEY CLUSTERED (group_id)
)

CREATE TABLE Group_Accounts (
    group_id      INT NOT NULL,
    account_id    INT NOT NULL,
    is_primary    BIT NOT NULL,
    CONSTRAINT PK_Group_Accounts PRIMARY KEY CLUSTERED (group_id, account_id)
)

虽然它看起来像许多标准:许多关系,但帐户实际上并不属于多个组。我立即想到,“好的,我们可以将group_id放入Accounts表中,这应该有效。”但那么我将如何处理is_primary属性?

我可以将account_id放入Groupsprimary_account_id,然后我相信我可以使用primary_account_id, group_id to account_id, group_id上的外键强制执行RI。

或者,我可以将“is_primary”标志移动到Accounts表中。也许那是最好的解决方案?

对每种方法的优缺点有何看法?我错过了任何潜在的问题吗?还有其他一些我错过的选择吗?

是否有任何方法可以在触发器之外的任何情况下强制执行组内的单个主要帐户(主要是声明性RI)?

谢谢!

3 个答案:

答案 0 :(得分:2)

关系基数

根据您的描述判断,您需要1:N关系,这意味着您需要联结表Group_Accounts。只需从AccountsGroups的简单FK即可。

特殊行

接下来的问题是如何在N侧选择一行(Accounts)为“特殊”。你可以:

  1. 使用Accounts.is_primary标志并通过过滤的唯一索引(如果您的DBMS支持)强制执行其唯一性(每个组),
  2. 或者您可以在Groups中指向主要帐户的FK。但是,在后一种情况下,您必须小心选择实际属于的主帐户。
  3. 第二种方法可以模仿:

    enter image description here

    Groups.FK1表示:

    FOREIGN KEY (group_id, primary_account_no) REFERENCES Accounts (group_id, account_no)
    

    上述FK中group_id的存在是强制主帐户属于其作为主要帐户的群组的原因。

    在创建新帐户时,请注意如何生成account_no。您需要执行something like this以避免并发环境中的竞争条件(当然,实际代码会因DBMS而变化)。

    如果您的DBMS支持过滤索引,则选择第一种方法,并且没有特别的理由选择第二种方法。

    选择第二个if:

    • 您的DBMS不支持过滤索引,
    • 或您的DBMS支持延迟约束,您需要始终强制存在主帐户(只需使primary_account_no NOT NULL),
    • 或者您实际上并不需要account_id,因此您可以减少一个索引(取决于您的DBMS在FK上需要索引的严格程度,以及您的实际工作量,您可以避免索引{ {1}},而不是必须出现在primary_account_no上的索引。)

答案 1 :(得分:1)

绝对可以摆脱Group_Accounts。

根据您的说明,似乎每个群组都有多个帐户,但每个帐户只有一个群组。因此,您可以按照建议将group_id放入Accounts表中,然后将primary_account_id作为字段放入Groups。

答案 2 :(得分:1)

可以通过将PK更改为帐户ID而不是帐户和组来将m:n交集表Group_Accounts更改为1:n表。但是,您仍然会受到执行约束的额外开销,即只有一个帐户是任何组的主要帐户。

但是,如果将组FK移动到帐户记录(实际上应该是1:n基数),则可以创建类似Group_Accounts表的Primary_Accounts表,除了PK将是组ID。因此,每个组只能有一个条目,那将是一个主要帐户。它看起来像这样:

create table Groups (
    Id      int not null,
    Name    varchar( 50 ) not null,
    constraint PK_Groups primary key( Id )
);

create table Accounts (
    Id      int not null,
    Name    varchar( 50 ) not null,
    GroupID int not null,
    constraint PK_Accounts primary key( Id ),
    constraint FK_AccountGroup foreign key( GroupID )
        references Groups( ID )
);

create table PrimaryAccounts (
    GroupID      int not null,
    AccountID    int not null,
    constraint PK_PrimaryAccounts primary key( GroupId ),
    constraint FK_PrimaryGroup foreign key( GroupID )
        references Groups( ID ),
    constraint FK_PrimaryAccount foreign key( AccountID )
        references Accounts( ID )
);

现在你已经正确地使用了1:n基数设计,你可以将每个组中只有一个帐户指定为主要帐户。

然而,有一个缺陷。 PrimaryAccounts表必须引用现有组和现有帐户,但没有任何内容强制执行帐户与组关联的隐式要求。

幸运的是,这很容易解决。只需在Accounts表中添加一个约束:

    constraint UQ_AccountGroup unique( GroupID, ID ),

然后,您不需要在PrimaryAccounts表中创建两个FK,而只需要一个:

    constraint FK_PrimaryGroupAccount foreign key( GroupID, AccountID )
        references Accounts( GroupID, ID )

现在每个组只能有一个主帐户,并且该帐户必须与该组关联。