10

I'm trying to update a database that is maintained and deployed using a database project (.sqlproj) in Visual Studio 2012. This is easier with SQL Server Management Studio, but in this case I have to deploy using a DACPAC.

What is the correct way to change a column to not be nullable, using DACPAC and without risking data loss?

A nullable column was added to a table. Now I need to publish an update that sets the column to not null and sets a default. Because there are rows in the table, the update fails. There is a setting to 'allow data loss' but that isn't an option for us and this update should not result in data loss. Here's a simple example that shows the problem:

CREATE TABLE [dbo].[Hello]
(
    [Id] INT IDENTITY(100,1) NOT NULL PRIMARY KEY, 
    [HelloString] NVARCHAR(50) NULL , 
    [Language] NCHAR(2) NOT NULL
)

Now publish that database and add rows, at least one row should have a null for HelloString.

Change the table definition to be:

CREATE TABLE [dbo].[Hello]
(
    [Id] INT IDENTITY(100,1) NOT NULL PRIMARY KEY, 
    [HelloString] NVARCHAR(50) NOT NULL DEFAULT 'Hello' , 
    [Language] NCHAR(2) NOT NULL
)

This cannot be published.

Error:

Rows were detected. The schema update is terminating because data loss might occur.

Next, I tried to add a pre-deployment script to set all NULL to be 'Hello':

UPDATE Hello SET HelloString = 'Hello' WHERE HelloString IS NULL

This publish attempt also fails, with the same error. Looking at the auto generated publish script it is clear why, but this seems to be incorrect behavior.

  1. The NOT NULL alteration is applied BEFORE the default is added
  2. The script checks for ANY rows, it doesn't matter whether there are nulls or not.

The advice in the comment (To avoid this issue, you must add values to this column for all rows) doesn't solve this.

/*
The column HelloString on table [dbo].[Hello] must be changed from NULL to NOT NULL. If the table contains data, the ALTER script may not work. To avoid this issue, you must add values to this column for all rows or mark it as allowing NULL values, or enable the generation of smart-defaults as a deployment option.
*/

IF EXISTS (select top 1 1 from [dbo].[Hello])
    RAISERROR (N'Rows were detected. The schema update is terminating because data loss might occur.', 16, 127) WITH NOWAIT
GO

PRINT N'Altering [dbo].[Hello]...';
GO

ALTER TABLE [dbo].[Hello] ALTER COLUMN [HelloString] NVARCHAR (50) NOT NULL;
GO

PRINT N'Creating Default Constraint on [dbo].[Hello]....';
GO

ALTER TABLE [dbo].[Hello] 
ADD DEFAULT 'hello' FOR [HelloString];

Seen in SQL Server 2012 (v11.0.5343), SQL Server Data Tools 11.1.31009.1

mafue
  • 1,858
  • 1
  • 21
  • 27
  • Can you insert the default value into the null fields then do the update? – Holmes IV Aug 19 '15 at 17:52
  • @HolmesIV I'm trying to do that with the pre-deployment script but I will try executing it separately to see if that makes a difference. Thanks! – mafue Aug 19 '15 at 17:58

1 Answers1

8

When publishing a dacpac using SSMS, you'll not have access to the full set of publish options that are available when publishing from SqlPackage.exe or Visual Studio. I would suggest publishing with either SqlPackage.exe or with Visual Studio and enabling the "Generate smart defaults, where applicable" option. In the case of SqlPackage.exe, you would run a command like:

"C:\Program Files (x86)\Microsoft SQL Server\120\DAC\bin\SqlPackage.exe" /a:publish /sf:"C:\MyDacpac.dacpac" /tcs:"Data Source=MYSERVER;Initial Catalog=MYDATABASE;Integrated Security=true" /p:GenerateSmartDefaults=true

In the case of Visual Studio, you'd check the Generate smart defaults option in the Advanced publish options dialog.

Steven Green
  • 3,387
  • 14
  • 17
  • 'Generate smart defaults' is used to generate defaults for a column that has no default defined. That's not the case here, I have defined a default for my column. But looking at the autogen'd SQL, I can see that it's trying to apply my default after changing the column def to NOT NULL, so maybe your answer is a workaround. Is that what you meant? – mafue Aug 19 '15 at 22:06
  • Yes. You're on the right path with using the pre-deploy script to replace the null values with your desired value. Combining that with an enabled "generate smart defaults" setting will allow you to deploy successfully. There is an alternative to this, but it involves writing a [Deployment Contributor](https://msdn.microsoft.com/en-US/library/dn306642(v=vs.103).aspx), which is more work. – Steven Green Aug 19 '15 at 22:44
  • This works. It does the same thing as my pre-deploy script, but it also (most importantly) removes the bad line from the autogen'd script that throws if there are any rows (with nulls or without.) – mafue Aug 20 '15 at 21:25