【问题标题】:PRIMARY KEYs vs. UNIQUE Constraints主键与唯一约束
【发布时间】:2012-02-12 01:23:18
【问题描述】:

Alexander Kuznetsov article 中,他提供了以下代码 sn-p:

CREATE TABLE dbo.Vehicles(
    ID INT NOT NULL, 
    [Type] VARCHAR(5) NOT NULL,
    CONSTRAINT Vehicles_PK PRIMARY KEY(ID),
    CONSTRAINT Vehicles_UNQ_ID_Type UNIQUE(ID, [Type]),
    CONSTRAINT Vehicles_CHK_ValidTypes CHECK([Type] IN ('Car', 'Truck'))
);

这个 sn-p 向我提出了几个问题。

  1. 为什么必须在唯一约束中同时包含IDType?如果只有 ID 是唯一的,那么这两列的组合也将始终是唯一的。

  2. 另外,我知道如何设置主键并指定它在 SSMS 中是否唯一。但是我如何在一个列上指定一个主键,并对列的组合进行唯一约束?这会创建两个索引吗?

这是因为我正在尝试实现类似的代码,该代码不会创建复合主键,并且出现以下错误。所以我试图更好地理解这段代码。

“MyTable”表中的列与现有主键或 UNIQUE 约束不匹配。


编辑

我只需在MyTable 中创建一个复合主键就可以完成这项工作。实际的表定义如下所示。同样,这有效。但它与上面引用的代码不同。而且我不确定如果我这样做会不会更好。

CREATE TABLE [dbo].[MessageThread](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [MessageThreadType] [int] NOT NULL,
    CONSTRAINT [PK_MessageThread_1] PRIMARY KEY CLUSTERED 
    (
        [Id] ASC,
        [MessageThreadType] ASC
    ) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[MessageThread]  WITH CHECK ADD  CONSTRAINT [CK_MessageThread_ValidType] CHECK  (([MessageThreadType]=(2) OR [MessageThreadType]=(1)))
GO

ALTER TABLE [dbo].[MessageThread] CHECK CONSTRAINT [CK_MessageThread_ValidType]
GO

【问题讨论】:

  • 我认为您需要将两列都包含在唯一约束中,因为您希望引用此 2 列组合。 DBMS 需要对此组合使用唯一约束(和索引),以便它们可以强制执行 FK 引用(并快速执行)。
  • @Aaron:当我尝试从我正在创建的表中创建到 MyTable 的 FK 时,我在 SSMS 中遇到了这个错误。
  • @ypercube:我认为这是正确的。虽然,正如我所指出的,它似乎不需要它,因为这种组合总是独一无二的。
  • MyTable 是否有唯一约束或主键?在哪些列上?
  • 我分享你的困惑。在这种情况下,使用 UNIQUE 约束 (ID,type) 作为主键似乎是合适的。 ID 列将不需要索引,因为它被 ID,Type 复合索引覆盖。或许展示您的表格描述会让我们更好地了解您的问题。

标签: sql-server sql-server-2008 foreign-keys


【解决方案1】:

1:我不确定给定架构的具体用途。但请注意,可以出于多种原因应用唯一约束,最常见的是:(a) 强制 唯一性和 (b) 为优化器提供更多信息以进行基础决策。

2 :唯一约束不会创建两个索引。它创建一个索引,其中一列作为前导键列。它在两者上都强制执行唯一性。所以对 a,b 的唯一约束可能有:

a    b    
---- ----
1    1
1    2
2    1
2    2

请注意,这两列都没有单独强制唯一性。我不太喜欢在 SSMS 中使用表格设计器(它有很多错误并且不支持所有功能),但这里是如何做到的:

a) 右键单击​​网格并选择Indexes/Keys...

b) 使用 Columns 网格中的 [...] 按钮选择多列

c) 将 Type 更改为 Unique Key

d) 如果需要,更改 Name

这是一个已经有主键的表的示例。如果我愿意,我可以添加一个或多个唯一索引:

【讨论】:

  • 感谢@Aaron,但我仍在努力将其融入我熟悉的内容中。您能帮我理解上述步骤与在这两列上简单地创建复合主键有何不同吗?
  • 对于主键,您只需单击+选择一列或多列,右键单击,然后说“设置主键”。我仍然建议通过 UI 学习如何做到这一点并不重要。学习 DDL 语法,因为它比 UI 更不频繁地更改,具有更少的错误 (0),并且可以编写脚本并将其存储在源代码管理中(因此复制)比在 UI 中点击和点击要容易得多。
  • 我将添加另一个屏幕截图。如果已经有主键,仍然可以添加多个唯一索引(但一张表只能有一个主键)
  • 我了解您对使用 DDL 语法的偏好。我绝不是新手开发人员,但 SQL 不是我的第一语言,我对它的大部分经验都是使用 UI。我很高兴学习更多 DDL 语法,我可以通过了解这里讨论的语法与我熟悉的 UI 命令之间的关系来做到这一点。
  • 顺便说一句,我确实在 MyTable 中创建了一个复合主键,然后成功创建了我一直在尝试创建的 FK。也许下一步是编写表格并查看它。
【解决方案2】:

据我了解,对ID,[Type] 进行唯一约束的原因是让详细表将ID,[Type] 作为外键引用。通常要求父表对用于外键的列具有唯一约束。例如,问题中的表可以有 2 个明细表:

CREATE TABLE dbo.CARS( 
....
vehicle_id INT NOT NULL,
[Type] VARCHAR(5) NOT NULL,
CONSTRAINT CAR_CHK_TYPE CHECK [Type]='Car',
CONSTRAINT CAR_FK_VEHICLE FOREIGN KEY (vehicle_id,[Type]) REFERENCES Vehincle(id,[Type]));

CREATE TABLE dbo.TRUCKS( 
....
vehicle_id INT NOT NULL,
[Type] VARCHAR(5) NOT NULL,
CONSTRAINT CAR_CHK_TYPE CHECK [Type]='Truck',
CONSTRAINT CAR_FK_VEHICLE FOREIGN KEY (vehicle_id,[Type]) REFERENCES Vehincle(id,[Type]));

这样Cars 将仅包含有关Car 类型的详细信息,而TRUCKS 仅包含有关Truck 的详细信息。

这样的设计是为了避免多态关系,比如

CREATE TABLE dbo.VEHICLE (
...,
ref_id INT NOT NULL,
-- PK of 'master' table
ref_name VARCHAR(20) NOT NULL, 
-- here we put 'truck' or 'car', so we virtually have 2 parents; 
-- in this case we cannot use FK constraint, the only thing that may 
-- somehow enforce the logical constraint is writing a trigger

更新

您更新后的表定义对我来说看起来不错。我猜这个示例表最初是为 Oracle 设计的,然后移植到 SQLServer。在 Oracle 中,唯一约束和主键可以使用相同的索引,因此同时具有 PK 和唯一约束不会受到惩罚。

【讨论】:

  • 谢谢@a1ex07。即使这对总是独一无二的,情况似乎也是如此。您是否知道如何使用 SSMS GUI 设置此配置?它只是一个复合主键吗?或者有没有办法指定ID为主键,然后再添加一个引用两个列的UNIQUE约束?
  • 我认为对于 SQLServer 来说,只有复合主键是可以的。据我所知,SQLServer(至少到 2008 年)不允许您创建没有索引的唯一约束。
【解决方案3】:
  1. 好问题。理论上你是对的;没有任何理由,一条记录总是可以由它的 PK 唯一标识,并且只要这是真的,唯一的约束就会一直得到满足。但是,如果 ID 和 Type 在数据层的范围之外有某种关系(也许这个表是 Enum 的数据模型?),那么不太可能有两个不同的 ID 具有相同的 Type,因为 Type 的唯一性是在其他地方强制执行。该约束还设置了一个同时包含 ID 和 Type 的索引,从而使通过该列组合查询表相对高效。

  2. 您使用“管理索引和键”选项设置了唯一约束。是的,这将为主键创建索引和唯一约束,并为 PK 和 Type 的组合创建索引和唯一约束。

【讨论】:

    【解决方案4】:

    我怀疑将两列都包含在 UNIQUE 约束中的原因与您提到的错误消息有关。 SQL Server(与其他 SQL DBMS 一样)有一个限制,即 FOREIGN KEY 约束只能准确引用由唯一性约束定义的列集。因此,如果 FOREIGN KEY 约束引用了两列,那么这两个列必须对它们有唯一性约束——即使其他约束已经保证了唯一性。这是一个毫无意义的限制,但它是标准 SQL 的一部分。

    以下示例非常相似,并解释了为什么复合外键和嵌套唯一性约束很有用。

    http://consultingblogs.emc.com/davidportas/archive/2007/01/08/Distributed-Keys-and-Disjoint-Subtypes.aspx

    【讨论】:

    • 遗憾的是,SQL Server 通过创建冗余的附加索引来强制保证真正的逻辑唯一约束。我在备份唯一约束的索引中看到了关于 INCLUDE d 列的连接项。这在这里可能很有用。 UNIQUE(ID) INCLUDE ([Type]) 上的索引可用于执行逻辑约束和备份 FK。
    【解决方案5】:

    给你:

    1. 汽车和卡车具有不同的属性,因此它们不属于一个表。这就是为什么我有两张桌子,Cars 和 Trucks。

    2. 然而,汽车和卡车共享一些属性,例如 VIN(车辆识别号)。更重要的是,VIN 是独一无二的。这就是为什么我需要一张桌子车辆。车辆不能既是汽车又是卡车,所以我必须确保不能同时输入 (VIN=123456789, Type=Car) 和 (VIN=123456789, Type=Truck)。这就是为什么我只有 VIN 的 PK。

    3. 我必须确保车辆不能在 Cars 和 Trucks 表中都有相应的行。这就是为什么我在 Cars 和 Trucks 中有 Type 列,这就是为什么我希望子表 Cars 和 Trucks 中的 (VIN, Type) 引用父表 Vehicles。我需要对 (VIN, Type) 附加唯一约束的唯一原因是:它由子表中的 FK 约束引用。

    顺便说一句,您可以在博客上发表评论 - 在这种情况下,sqlblog 会向我发送消息。我在这里注意到你的问题是一个巧合;本来应该去滑雪的,只是没有下雪。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-28
      • 2020-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多