tags:

views:

53

answers:

2

I have this SQL change script that runs as part of my nant orchestrated DB creation or update:

SET XACT_ABORT ON
BEGIN TRANSACTION

PRINT 'Change MyColumn column to MyNewColumn column in MyTable table'
IF EXISTS (SELECT *
           FROM sys.columns
           WHERE Name = 'MyColumn' AND Object_id = OBJECT_ID('[dbo].[MyTable]'))
BEGIN
    PRINT '-> Exists, change it'
    /* NOTE THE NEXT LINE */
    SET @Value = (SELECT MyColumn FROM [dbo].[MyTable])

    ALTER TABLE [dbo].[MyTable]
    DROP CONSTRAINT DF_MyTable_MyColumn

    ALTER TABLE [dbo].[MyTable]
    DROP COLUMN MyColumn

    ALTER TABLE [dbo].[MyTable]
    ADD MyNewColumn nvarchar(20) NULL

    ALTER TABLE [dbo].[MyTable]
    ADD CONSTRAINT DF_MyTable_MyNewColumn DEFAULT ('') FOR MyNewColumn

    PRINT '-> Add values back into table'
    SET @Dynamic_Sql = 'UPDATE [dbo].[MyTable] SET MyNewColumn = ''' + @Value + ''''
    EXEC(@Dynamic_Sql)

    PRINT '-> Alter to NOT NULL'
    ALTER TABLE [dbo].[MyTable]
    ALTER COLUMN MyNewColumn nvarchar(20) NOT NULL
END
ELSE
BEGIN
PRINT '-> Does not exist, skip it'
END

I have already ran this update script before and made the changes to the DB (so MyColumn no longer exists). But now I have a new script that comes after this one, but my "build" fails on this line of this script with:

Msg 207, Level 16, State 1, Line 15 Invalid column name 'MyColumn'

where Line 15 is the FROM sys.columns line. But this is actually complaining about the line I have within the IF statement, where I have put in the NOTE comment. Why would this be the behaviour? Of course the column name will be invalid if it no longer exists.

+2  A: 

Do you include the GO batch separator after you create all of your columns? If not, the columns won't be created by the time your first query runs, because the query parser parses it all at the same time -- at parse time, the column really doesn't exist.

By adding the GO batch separator, you force it to parse the portions of the query which use your newly created columns after the columns are actually created.

Dave Markle
any idea error msg relates to the "FROM sys.columns line. "
Conrad Frix
The line number is often incorrect if you're simply comparing it to a script. SQL Server will reset the line counter when it hits a GO statement. There are other things that may throw off the line # as well.
Tom H.
That "Line 15" message may be a red herring, throwing you off. Sometimes those messages are inaccurate depending upon how you run the query.
Dave Markle
Thanks for all the feedback. I have updated the question to show more of the script because I have found where the actual failure is occurring, and I'm not experienced enough to understand why the execution of the script is forward looking within an IF statement.
Chris F
+2  A: 

The problem (as Dave Markle alludes to, so feel free to accept his answer) is that SQL Server parses the entire section of the script. It sees that you're referring to MyColumn and that column doesn't exist, so it gives you the error. It doesn't matter that it's within an IF statement.

You can test it easily with this script:

CREATE TABLE dbo.Test (my_id int)
GO
IF (1=0)
    SELECT blah FROM Test

If I can find a way to defer the parsing, I'll update this answer, but other than using dynamic SQL I don't think that you can.

EDIT: Here's one possible solution. I didn't go through Martin's link yet, but that may be another.

CREATE FUNCTION dbo.Get_my_id ()
RETURNS INT
AS
BEGIN
    DECLARE @my_id INT
    SELECT @my_id = blah FROM dbo.Test
    RETURN @my_id
END
GO
CREATE TABLE dbo.Test (my_id INT)
GO
DECLARE @my_id INT

IF (1=0)
    SELECT @my_id = dbo.Get_my_id()
GO

BTW, if your table has more than one row in it, you realize that the value of your variable cannot be predicted, correct?

Tom H.
@Tom I see, now I follow with what Dave M. was saying. I guess I'll have to figure out a way around this.
Chris F
@Tom you can defer it with a bit of a kludgey workaround! http://stackoverflow.com/questions/3290786/is-it-possible-to-tell-ssms-not-to-check-if-a-column-exists-in-a-t-sql-script/3291626#3291626
Martin Smith
Thanks Martin. I've posted another possible method above.
Tom H.
@Tom, why can the value of the variable not be predicted if more than one row exists in the column?
Chris F
@Chris - Which row will be used to fill in the variable? You can't assign 5 values to one integer variable. Since you don't use `TOP` with an `ORDER BY` or some kind of aggregate function (`MAX`, `MIN`, etc.) you can't be sure which value SQL Server will happen to put into it. You might see some consistency because SQL Server *generally* gets those results in a particular order and just happens to use the last value, but without an `ORDER BY` that's not guaranteed.
Tom H.
@Tom, The line where you say SELECT @my_id = blah FROM dbo.Test fails for me stating "Procedure Get_my_id Invalid column name 'blah'".
Chris F
My bad... deferred name resolution only works for non-existent tables, not non-existent columns in an existing table. You can get around that by changing the order of the script (which I'm going to do to my answer), but if you're dealing with column renames that solution wouldn't be applicable because the table will already exist. :(
Tom H.