Renaming a column in SQL Server, and what it quietly breaks
A column was misspelled when the table was created, and it has been Ammount ever since. The
rename itself is one line:
EXEC sp_rename 'dbo.Invoice.Ammount', 'Amount', 'COLUMN';
Caution: Changing any part of an object name could break scripts and stored procedures.
That caution is the entire problem, stated once and never mentioned again. The rename succeeds. What breaks, breaks later, and in a different file.
Note the arguments: the old name is qualified with the table, the new name is not — write
'dbo.Invoice.Amount' as the second argument and you get a column called dbo.Invoice.Amount,
brackets and all.
What follows the rename by itself
More than people expect.
Indexes keep working. The index still indexes the same column under its new name:
CREATE INDEX IX_Tag_Labell ON dbo.Tag (Labell);
EXEC sp_rename 'dbo.Tag.Labell', 'Label', 'COLUMN';
The index is intact and now covers Label. Its own name, though, is untouched — you are left with
IX_Tag_Labell on a column called Label, which is how a schema starts lying about itself. Rename
the index too:
EXEC sp_rename 'dbo.Tag.IX_Tag_Labell', 'IX_Tag_Label', 'INDEX';
Foreign keys keep working, and stay trusted — is_not_trusted remains 0. Nothing to do.
Unique constraints keep working, like indexes.
Default constraints keep working, and usually have nothing to rewrite — DEFAULT (0) is stored
as ((0)) and names no column at all. The constraint's own name is untouched, so
DF_Invoice_Ammount is still sitting on a column called Amount.
One case that refuses outright
A check constraint stops the rename dead:
CREATE TABLE dbo.ChkOnly (Id int NOT NULL PRIMARY KEY,
Qty int NOT NULL CONSTRAINT CK_ChkOnly_Qty CHECK (Qty >= 0));
EXEC sp_rename 'dbo.ChkOnly.Qty', 'Quantity', 'COLUMN';
Msg 15336, Level 16, State 1, Procedure sp_rename, Line 563
Object 'dbo.ChkOnly.Qty' cannot be renamed because the object participates in enforced dependencies.
The constraint's definition names the column, and SQL Server will not leave it pointing at a name that no longer exists. Drop it, rename, put it back:
ALTER TABLE dbo.ChkOnly DROP CONSTRAINT CK_ChkOnly_Qty;
GO
EXEC sp_rename 'dbo.ChkOnly.Qty', 'Quantity', 'COLUMN';
GO
ALTER TABLE dbo.ChkOnly ADD CONSTRAINT CK_ChkOnly_Quantity CHECK (Quantity >= 0);
GO
Those GOs matter. Without them the whole thing is one batch, the ADD CONSTRAINT is parsed
before the rename has happened, and it fails with Invalid column name 'Quantity' — leaving you
with the constraint dropped and the column renamed, which is the worst of the three states.
A schema-bound view refuses for the same reason, with the same message:
CREATE VIEW dbo.PaymentBound WITH SCHEMABINDING AS SELECT PaymentId, Ammount FROM dbo.Payment;
EXEC sp_rename 'dbo.Payment.Ammount', 'Amount', 'COLUMN';
Msg 15336, Level 16, State 1, Procedure sp_rename, Line 563
Object 'dbo.Payment.Ammount' cannot be renamed because the object participates in enforced
dependencies.
Both of these are the helpful outcome: they are the dependencies that will not let you break them. Everything in the next section breaks silently instead.
What does not follow
Views, stored procedures, functions and triggers hold your text, not a reference to the column. They are not updated, and they do not complain until something runs them:
SELECT * FROM dbo.InvoiceTotal;
Msg 207, Level 16, State 1, Procedure InvoiceTotal, Line 1
Invalid column name 'Ammount'.
Msg 4413, Level 16, State 1, Line 1
Could not use view or function 'dbo.InvoiceTotal' because of binding errors.
EXEC dbo.GetInvoice @Id = 1;
Msg 207, Level 16, State 1, Procedure dbo.GetInvoice, Line 1
Invalid column name 'Ammount'.
A deployment that renames a column therefore succeeds, reports no error, and leaves a procedure that fails the first time a customer calls it. That gap — between the change and the failure — is the whole risk of the operation.
Find them before you rename
Everything that uses the table:
SELECT referencing_schema_name AS SchemaName, referencing_entity_name AS UsesTheTable
FROM sys.dm_sql_referencing_entities('dbo.Invoice', 'OBJECT');
SchemaName UsesTheTable
---------- ------------
dbo GetInvoice
dbo InvoiceTotal
That is the list to read before you decide. It will not tell you which ones name the column, so after the rename, search the definitions for the old name:
SELECT OBJECT_SCHEMA_NAME(m.object_id) AS SchemaName,
OBJECT_NAME(m.object_id) AS ObjectName,
o.type_desc AS Kind
FROM sys.sql_modules AS m
JOIN sys.objects AS o ON o.object_id = m.object_id
WHERE m.definition LIKE '%Ammount%';
SchemaName ObjectName Kind
---------- ---------- --------------------
dbo GetInvoice SQL_STORED_PROCEDURE
Fix each one with ALTER VIEW or ALTER PROCEDURE, then run that query again. When it returns no
rows, nothing left in the database names the old column. It is the only check at the end of this
job that means anything.
Two caveats on it. It searches text, so a column named Code matches far too much — rename
misspellings and distinctive names with confidence, and read the results rather than trusting the
count. And it sees only what is stored in the database: your application's SQL, reports, ETL jobs
and anything built with dynamic SQL are outside it entirely.
The safer shape, when the column is in use
A rename is instant and total: the old name is gone for every reader at the same moment. Where an application is deployed separately from its database, that is a window of failure you cannot close by sequencing.
The alternative takes longer and never breaks:
- Add the new column, and keep both written for a while.
- Move the readers across, one at a time, deploying as you go.
- Drop the old column when nothing names it.
Worth the extra deployments only where the downtime matters — but when it matters, nothing else does the job.
Knowing what points at the table
The lists above come from the database, which is a better source than memory. What they do not show is the shape of the thing: which tables point at this one, which columns join to what, and therefore how far a rename travels.
WoodFireERD draws that from your schema — pick the table, follow its relationships out as far as you want to look, and take the change out as an ordered migration script.