使用SSDT,如何创建具有唯一约束的列?

时间:2014-05-20 20:06:25

标签: sql-server sql-server-data-tools

我们有一张桌子:

`CREATE TABLE dbo.Account ( 
AccountID INT NOT NULL PRIMARY KEY IDENTITY,
AccountName NVARCHAR(100) NOT NULL);

我想添加一个PartnerAccountKey列并为其添加一个唯一约束(在我假设的部署后脚本中填充数据)

CREATE TABLE dbo.Account ( 
AccountID INT NOT NULL PRIMARY KEY IDENTITY,
AccountName NVARCHAR(100) NOT NULL,
PartnerAccountKey  INT NOT NULL,
CONSTRAINT UK_Account_PartnerKey UNIQUE (PartnerAccountKey));

问题是在我的部署后脚本运行之前创建了唯一约束,因此它出错。我没有看到如何在创建列和创建唯一约束之间填充数据。

2 个答案:

答案 0 :(得分:6)

我也发现了这方面的问题,经过几个小时尝试不同的方法后,我提出了以下两种方法。

这些是变通方法,可能并不理想,但希望它们有所帮助。

1)需要SQL2008或更高版本。

基本上,这利用了直到部署后脚本之后才应用CHECK CONSTRAINTS这一事实。

因此,我创建了一个过滤的唯一索引,该索引排除了默认值,然后添加了一个检查约束以防止使用默认值。

但是,您需要选择一个您知道不会在生产中使用的值。对于这个例子,我假设我永远不会有一个PartnerAccountKey值为零。但是,您可以通过在表定义中创建默认值来选择任何数字。

我的发布选项包括"生成智能默认设置(如果适用)"和#34;新约束的脚本验证"

我更改了您的表定义,如下所示。

CREATE TABLE [dbo].[Account]
(
AccountID  INT NOT NULL PRIMARY KEY IDENTITY(1,1),
AccountName NVARCHAR(100) NOT NULL,
PartnerAccountKey INT NOT NULL 
);
GO
ALTER TABLE dbo.Account 
    ADD CONSTRAINT CK_PartnerAccountKey CHECK (PartnerAccountKey!=0);
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_Acocunt_PartnerKey] 
    ON dbo.[Account] (PartnerAccountKey) WHERE PartnerAccountKey!=0;
GO

发布脚本运行时

  • 1)使用智能默认值0
  • 创建列
  • 2)添加唯一索引视图
  • 3)添加检查约束WITH NOCHECK

在您的帖子部署脚本中,您可以填充正确的值

IF EXISTS (SELECT 1 FROM dbo.Account WHERE PartnerAccountKey=0)
BEGIN
    -- Migrate Data Here - Example

    UPDATE 
        dbo.Account 
    SET 
        PartnerAccountKey       = AccountID;
END;

以下是发布脚本中的重要部分。

ALTER TABLE [dbo].[Account]
    ADD [PartnerAccountKey] INT NOT NULL, 
    CONSTRAINT [SD_Account_cb8fbf98ba884b92b63b4b2017b7de20] 
    DEFAULT 0 FOR [PartnerAccountKey];

....

ALTER TABLE [dbo].[Account] 
    DROP CONSTRAINT [SD_Account_cb8fbf98ba884b92b63b4b2017b7de20];

....

CREATE UNIQUE NONCLUSTERED INDEX [IX_Acocunt_PartnerKey]
    ON [dbo].[Account]([PartnerAccountKey] ASC) 
    WHERE PartnerAccountKey!=0;    

....

ALTER TABLE [dbo].[Account] WITH NOCHECK
    ADD CONSTRAINT [CK_PartnerAccountKey] 
    CHECK (PartnerAccountKey!=0);

....

/*
    Post-Deployment Script Template                         
*/
IF EXISTS (SELECT 1 FROM dbo.Account WHERE PartnerAccountKey=0)
BEGIN
    -- Migrate Data Here

    UPDATE 
        dbo.Account 
    SET 
        PartnerAccountKey       = AccountID;
END;

/*
   End - Post Deploy
*/

....

ALTER TABLE [dbo].[Account] 
    WITH CHECK CHECK CONSTRAINT [CK_PartnerAccountKey];

2)需要SQL2012或更高版本。

这种方法可能更干净,但您可能需要在某些时候重新访问脚本以进行清理。

它使用序列来确保新创建的列具有唯一值。

  • 1)创建序列
  • 2)使用此序列作为新列的默认值
  • 3)创建唯一约束
  • 4)在部署后脚本中用适当的数据替换值。
  • 5)可选择删除默认值和序列。

如果您选择删除默认和/或序列,则下次发布时将再次创建。这不是一个真正的问题,因为部署后的脚本只会再次删除它们。

首先创建序列。我决定从-1开始向后计数,这样我就可以确定这些不是真正的价值。

CREATE SEQUENCE [dbo].[TempSequence]
    AS INT
    START WITH -1
    INCREMENT BY -1
    NO MAXVALUE
    NO CYCLE
    CACHE 10;

接下来是表格脚本。

CREATE TABLE [dbo].[Account]
(
    AccountID   INT NOT NULL PRIMARY KEY IDENTITY(1,1),
    AccountName VARCHAR(20) NOT NULL,
    PartnerAccountKey INT NOT NULL CONSTRAINT [DF_PartnerAccountKey] 
        DEFAULT (NEXT VALUE FOR TempSequence),
    CONSTRAINT [UK_Account_PartnerKey] UNIQUE (PartnerAccountKey)
);

后部署脚本。

IF EXISTS (SELECT 1 FROM dbo.Account WHERE PartnerAccountKey < 0)
BEGIN 
    -- Example - Migrate Data

    UPDATE 
        dbo.Account
    SET 
        PartnerAccountKey           = AccountID; 
END;

GO
-- Optional Remove the default 
IF EXISTS (SELECT 1 FROM sys.default_constraints WHERE 
    name = 'DF_PartnerAccountKey' AND object_id=OBJECT_ID(N'dbo.Account'))
BEGIN 
    ALTER TABLE dbo.Account DROP CONSTRAINT [DF_PartnerAccountKey];
END;
GO
-- Optional Remove the sequence 
IF EXISTS (SELECT 1 FROM sys.sequences WHERE name = 'TestSequence')
    BEGIN
        DROP SEQUENCE TestSequence;
    END;
GO

以下是生成的发布脚本的相关部分。

CREATE SEQUENCE [dbo].[TempSequence]
    AS INT
    START WITH -1
    INCREMENT BY -1
    CACHE 10;

....

ALTER TABLE [dbo].[Account]
    ADD [PartnerAccountKey] INT CONSTRAINT [DF_PartnerAccountKey] 
    DEFAULT ( NEXT VALUE FOR TempSequence) NOT NULL;

....

ALTER TABLE [dbo].[Account]
    ADD CONSTRAINT [UK_Account_PartnerKey] UNIQUE NONCLUSTERED 
    ([PartnerAccountKey] ASC);

....

/*
     Post-Deployment Script Template                            
*/
IF EXISTS (SELECT 1 FROM dbo.Account WHERE PartnerAccountKey < 0)
BEGIN 
        -- Example - Migrate Data

        UPDATE 
            dbo.Account
        SET 
            PartnerAccountKey= AccountID; 
    END;

-- Optional Remove the default 
IF EXISTS (SELECT 1 FROM sys.default_constraints WHERE 
    name = 'DF_PartnerAccountKey' AND object_id=OBJECT_ID(N'dbo.Account'))
BEGIN 
    ALTER TABLE dbo.Account DROP CONSTRAINT [DF_PartnerAccountKey];
END;

-- Optional Remove the sequence 
IF EXISTS (SELECT 1 FROM sys.sequences WHERE name = 'TestSequence')
BEGIN
        DROP SEQUENCE TestSequence;
    END;

我在SQL2012 Developer Edition,Visual Studio 2013 Premium和SSDT 12.0.40403.0上测试了这两种方法。

答案 1 :(得分:1)

最好的方法是在两个版本中执行此操作 - 一个用于添加列,另一个用于添加约束 - 或者将Unique约束暂时放入Post-Deploy脚本中,然后在发布后将其添加回项目。 (或在创建之前检查是否存在)

我发现在尝试创建设置了ONLINE = ON或MAXDOP选项的索引时,我必须做类似的事情。 SSDT在创建索引时会忽略这些选项,但我可以在部署后的脚本中创建它们,然后将它们移动到项目的主要部分。