如何设计数据库表以强制执行非重复的唯一键记录

时间:2012-04-04 12:43:29

标签: sql sql-server tsql sql-server-2008-r2 constraints

我有一个SQL Server表:

+----+-------------+-------------------+
| ID | CompanyID   | CompanyCode       |
+----+-------------+-------------------+
| 1  | 1           | AAAA-123          |
| 2  | 2           | BBBB-111          |
| 3  | 1           | AAAA-123          |
| 4  | 3           | CCCC-999          |
| 5  | 3           | CCCC-999          |
| 6  | 1           | AAAA-123          |
+----+-------------+-------------------+

ID字段是PK。

CompanyIDCompanyCode字段指定了一个独特的公司,CompanyID始终具有相同的CompanyCode(反之亦然),并且没有两家公司会拥有相同的CompanyID和/或CompanyCode

我想在表格上创建一条规则,如果CompanyCodeCompanyCode不匹配CompanyID,则永远不会允许将记录添加到表中匹配。

以下是我不允许在桌面上发生的一个例子:

+----+-------------+-------------------+
| ID | CompanyID   | CompanyCode       |
+----+-------------+-------------------+
| 1  | 1           | AAAA-123          |
| 2  | 1           | BBBB-111          |<<<< This record should not be allowed
| 3  | 1           | AAAA-123          |
| 4  | 3           | CCCC-999          |
| 5  | 3           | CCCC-999          |
| 6  | 1           | AAAA-123          |
+----+-------------+-------------------+

请注意ID=2的记录,CompanyCode BBBB-111与现有CompanyCode of AAAA-123的记录不匹配。

我希望这个规则以某种方式存在于表中 - 即我不希望此规则成为查询和/或存储过程必须管理的业务规则。

我认为您可以说我希望强制执行重复记录(CompanyIDCompanyCode)当且仅当现有CompanyID和/或CompanyCode存在时在表中。

这可以在桌面设计级别进行吗?还是我不得不在我的脚本中管理它?

更新

虽然这与我的OP不太相关,但根据我收到的反馈,我想我应该对CompanyID / CompanyCode表设计给出一些背景知识。

首先,这只是一个模拟表设计,试图解释我的问题 - 我的真实表与公司无关。

其次,我的真实表是两个Web服务之间的中间人,其中Service1创建DeviceID而Service2 收集存在于微设备中的SerialNumber 。此外,我的真实表包含此中间人服务器必须处理的许多其他列。

这个中间人服务的一个奇怪的事情是,我正在谈论的表必须允许NULLS同时使用DeviceID和SerialNumber - 这一切都取决于哪个服务首先发送了一个记录....我我说的太多了,并且偏离了我原来问题的主题,但我认为我必须澄清我的表格例子,因为我在打破正常化方面得到了很多抨击。

5 个答案:

答案 0 :(得分:4)

 CREATE UNIQUE INDEX idx_name ON TableName(CompanyID, CompanyCode)

(假设您的表名为TableName)。

但是,您应该知道此设计违反了数据库规范化的原则。正确规范化的数据库将具有仅具有CompanyID和CompanyName字段的单独表。然后,您可以修改示例中的表以仅包含CompanyID字段;在需要时,可以“查找”CompanyCode(使用VIEW)。

此设计具有以下优点:

  1. 使用不同名称的公司ID几乎不可能。

  2. 如果公司更改了代码,您只需要在单个表中更新一条记录,该记录具有防错和更快的速度。

  3. 您可以节省存储空间。

  4. 它有一个小缺点,你必须加入Company表来获取代码。在关系数据库中,这不一定是真正的缺点(加入规范化数据是 的关系数据库),但你甚至可以通过删除CompanyID并将代码作为公司的主键来消除它。如果代码是不可变的,我只会考虑这个问题(对于给定的公司,它不会随着时间的推移而改变)。

答案 1 :(得分:2)

我建议您不要将CompanyID和CompanyCode存储在表中。相反,您应该有另一个表,为每个CompanyId存储1行,并且还有相应的代码。然后,您可以设置外键以确保此表中的CompanyId存在于另一个表中。

答案 2 :(得分:2)

我不知道制定规则来强制执行此操作,但是您使用两个单独的外键来执行相同的操作(总是会匹配)...最好不要使用公司代码这个表根本就是当你需要数据时将它连接到另一个表上,或者以其他方式重新设计它

答案 3 :(得分:0)

您违反了第3范式。非密钥取决于另一个非密钥。 CompanyCode依赖于CompanyID。其他人声明的补救措施是创建一个CompanyID,CompanyCode表,其中CompanyID是PK。从表中删除CompanyCode并在ComanyID上创建指向新表的FK关系。 http://www.phlonx.com/resources/nf3/ http://en.wikipedia.org/wiki/Third_normal_form

答案 4 :(得分:0)

好吧,这不是最漂亮的代码,但它确实强制执行约束。诀窍是创建一个索引视图,其上定义了两个唯一索引:

create table dbo.ABC (
    Col1 int not null,
    Col2 int not null
)
go
create view dbo.ABC_Col1_Col2_dep
with schemabinding
as
    select Col1,Col2,COUNT_BIG(*) as Cnt
    from
        dbo.ABC
    group by
        Col1,Col2
go
create unique clustered index IX_Col1_UniqueCol2 on dbo.ABC_Col1_Col2_dep (Col1)
go
create unique nonclustered index IX_Col2_UniqueCol1 on dbo.ABC_Col1_Col2_dep (Col2)
go

现在我们插入一些初始数据:

insert into dbo.ABC (Col1,Col2)
select 1,3 union all
select 2,19 union all
select 3,12

我们可以为Col1Col2添加另一行具有完全相同的值:

insert into dbo.ABC (Col1,Col2)
select 1,3

但是,如果我们选择Col2用于另一个Col1的值,反之亦然,我们会收到错误:

insert into dbo.ABC (Col1,Col2)
select 2,3
go
insert into dbo.ABC (Col1,Col2)
select 1,5

这里的诀窍是观察这个查询:

    select Col1,Col2,COUNT_BIG(*) as Cnt
    from
        dbo.ABC
    group by
        Col1,Col2

对于特定Col1值只有一行,并且只有一行具有特定Col2值,前提是您要执行的约束未被破坏 - 但是很快当一个非匹配的行插入到基表中时,该查询将返回多行。