Changing tables safely: ALTER and DROP
Add, change, rename and remove columns on tables that already hold data. See which changes are instant, which fail, and which silently lose data, and how to make schema changes you can undo.
ALTER TABLE changes a table in place, keeping its data. Some changes are harmless, some are refused, and a few quietly destroy data. Try them on BuildMart’s products:
Adding columns
ALTER TABLE dbo.Customers ADD Newsletter bit NOT NULL CONSTRAINT DF_Customers_Newsletter DEFAULT 0;GOSELECT TOP (3) CustomerId, FirstName, Newsletter FROM dbo.Customers;CustomerId FirstName Newsletter
---------- --------- ----------
1 Asha 0
2 Ben 0
3 Chloe 0Changing a column
ALTER COLUMN changes a column’s type, length or NULL rule. Widening (nvarchar(60) to nvarchar(100), int to bigint) is always safe. Narrowing is checked against the data and refused if anything would be cut. Changing to a different kind of type converts every value with the normal conversion rules, and those can lose data without an error, as the decimal-to-int example in the widget shows.
Renaming
sp_rename, not ALTER TABLE. SQL Server warns you, because every query, view and procedure that uses the old name breaks:
EXEC sp_rename 'dbo.Customers.Phone', 'PhoneNumber', 'COLUMN';GOSELECT TOP (2) CustomerId, FirstName, PhoneNumber FROM dbo.Customers;Caution: Changing any part of an object name could break scripts and stored procedures.
CustomerId FirstName PhoneNumber
---------- --------- -------------
1 Asha 0113 496 0001
2 Ben 01904 555 010Dropping columns, constraints and tables
ALTER TABLE … DROP COLUMN removes a column and its data. A column that a constraint uses cannot be dropped until the constraint is, which is where named constraints pay off: you can drop them by name. Tables are dropped with DROP TABLE, children before parents, because a table referenced by a foreign key cannot be dropped:
ALTER TABLE dbo.Customers ADD Region nvarchar(20) NULL CONSTRAINT DF_Customers_Region DEFAULT N'North';GOALTER TABLE dbo.Customers DROP COLUMN Region; -- fails: the default uses itGOALTER TABLE dbo.Customers DROP CONSTRAINT DF_Customers_Region;ALTER TABLE dbo.Customers DROP COLUMN Region;SELECT COUNT(*) AS RegionColumns FROM sys.columnsWHERE object_id = OBJECT_ID('dbo.Customers') AND name = 'Region';Msg 5074, Level 16, State 1, Line 1
The object 'DF_Customers_Region' is dependent on column 'Region'.
Msg 4922, Level 16, State 9, Line 1
ALTER TABLE DROP COLUMN Region failed because one or more objects access this column.
RegionColumns
-------------
0Schema changes you can undo
CREATE, ALTER and DROP take part in transactions like any INSERT or UPDATE. Wrap a risky change in a transaction, look at the result, and roll it back if it is wrong, data included:
BEGIN TRANSACTION; ALTER TABLE dbo.Products DROP COLUMN Unit;SELECT COUNT(*) AS UnitColumns FROM sys.columnsWHERE object_id = OBJECT_ID('dbo.Products') AND name = 'Unit'; ROLLBACK; SELECT TOP (2) Name, Unit FROM dbo.Products ORDER BY ProductId;UnitColumns
-----------
0
Name Unit
-------------- ----
Claw hammer each
Cordless drill eachBarcode varchar(13) NULL to Products, set it to 5012345678900 for the claw hammer (product 1), and make sure no two products can ever share a barcode while still allowing many products without one. Show the first two products with their barcodes.
Show a hint
Show the solution
ALTER TABLE dbo.Products ADD Barcode varchar(13) NULL;GOUPDATE dbo.Products SET Barcode = '5012345678900' WHERE ProductId = 1; CREATE UNIQUE INDEX UX_Products_Barcode ON dbo.Products (Barcode) WHERE Barcode IS NOT NULL; SELECT TOP (2) ProductId, Name, Barcode FROM dbo.Products ORDER BY ProductId;ProductId Name Barcode
--------- -------------- -------------
1 Claw hammer 5012345678900
2 Cordless drill NULLRemember
Adding a NULL column is instant; a NOT NULL column needs a DEFAULT on a table with rows. Widening a column is safe; narrowing is checked; changing the kind of type can silently lose data. Restate NULL / NOT NULL in ALTER COLUMN, and put GO before using a new column in the same script. Name constraints so they can be dropped; drop child tables before their parents. DDL is transactional in SQL Server: BEGIN TRANSACTION, change, check, then COMMIT or ROLLBACK.