When I run this in a single batch:
ALTER TABLE SomeTable ADD FOO int NULL;
alter table SomeTable drop column foo
ALTER TABLE SomeTable ADD FOO int NULL;
select top 1 FOO from SomeTable
... why in SQL Management studio is the end result that column FOO does not exist on table SomeTable?
The select statement gives an error, but no statement previous raises an error.
Adding GO statements would fix the issue, but why in a single batch does it act as it does?
"why in SQL Management studio is the end result that column FOO does not exist on table SomeTable?" It isn't. The problem is you are trying to reference a column in the same batch you performed an ALTER on the table you added it to; this results in the parsing error and the entire batch is not run.
Either split the SELECT into a separate batch (use GO in SSMS), or defer the parsing of the SELECT:
EXEC sys.sp_executesql N'SELECT TOP 1 FOO FROM dbo.SomeTable ORDER BY SomeColumn;';
Related
I have the following SQL in SQL Server 2005 but I get an error stating "Invalid column name 'ExpIsLocalTime' (ln 7) when I run it:
IF NOT EXISTS(SELECT * FROM sys.columns WHERE Name = N'ExpIsLocalTime' AND Object_ID = Object_ID(N'[dbo].[tbl_SessionsAvailable]'))
BEGIN
ALTER TABLE dbo.tbl_SessionsAvailable ADD
ExpIsLocalTime bit NOT NULL CONSTRAINT DF_tbl_SessionsAvailable_ExpIsLocalTime DEFAULT (0)
UPDATE dbo.tbl_SessionsAvailable
SET ExpIsLocalTime = 1
END
GO
This will be in a script file that may be run more than once so I'm trying to make sure the UPDATE only runs once. Is there something about BEGIN/END that delays the execution of the DDL statement?
Your SQL query to do the UPDATE refers to a column that has not yet been created. At compile time, SQL Server detects that the column does not exist, so it gives you the error "Invalid column name 'ExpIsLocalTime'".
In order to include the UPDATE in this query, you will need to encapsulate it in a dynamic SQL query. In other words, something like this:
IF NOT EXISTS(SELECT * FROM sys.columns WHERE Name = N'ExpIsLocalTime' AND Object_ID = Object_ID(N'[dbo].[tbl_SessionsAvailable]'))
BEGIN
ALTER TABLE dbo.tbl_SessionsAvailable ADD
ExpIsLocalTime bit NOT NULL CONSTRAINT DF_tbl_SessionsAvailable_ExpIsLocalTime DEFAULT (0)
DECLARE #SQL NVARCHAR(1000)
SELECT #SQL = N'UPDATE dbo.tbl_SessionsAvailable SET ExpIsLocalTime = 1'
EXEC sp_executesql #SQL
END
GO
We have the same issue in our SQL scripts that maintain tables. After a table is created, if we add a column to it later, we have to use dynamic SQL to avoid these compilation errors.
Another possibly simpler solution is using the GO statement after the Alter statement. This would send the DDL to the server. Then run the rest of your SQL. This should work if you are using sqlcmd osql or SSMS.
I have a a number of sp's that create a temporary table #TempData with various fields. Within these sp's I call some processing sp that operates on #TempData. Temp data processing depends on sp input parameters. SP code is:
CREATE PROCEDURE [dbo].[tempdata_proc]
#ID int,
#NeedAvg tinyint = 0
AS
BEGIN
SET NOCOUNT ON;
if #NeedAvg = 1
Update #TempData set AvgValue = 1
Update #TempData set Value = -1;
END
Then, this sp is called in outer sp with the following code:
USE [BN]
--GO
--DBCC FREEPROCCACHE;
GO
Create table #TempData
(
tele_time datetime
, Value float
--, AvgValue float
)
Create clustered index IXTemp on #TempData(tele_time);
insert into #TempData(tele_time, Value ) values( GETDATE(), 50 ); --sample data
declare
#ID int,
#UpdAvg int;
select
#ID = 1000,
#UpdAvg = 1
;
Exec dbo.tempdata_proc #ID, #UpdAvg ;
select * from #TempData;
drop table #TempData
This code throws an error: Msg 207, Level 16, State 1, Procedure tempdata_proc, Line 8: Invalid column name "AvgValue".
But if only I uncomment declaration AvgValue float - everything works OK.
The question: is there any workaround letting the stored proc code remain the same and providing a tip to the optimizer - skip this because AvgValue column will not be used by the sp due to params passed.
Dynamic SQL is not a welcomed solution BTW. Using alternative to #TempData tablename is undesireable solution according to existing tsql code (huge modifications necessary for that).
Tried SET FMTONLY, tempdb.tempdb.sys.columns, try-catch wrapping without any success.
The way that stored procedures are processed is split into two parts - one part, checking for syntactical correctness, is performed at the time that the stored procedure is created or altered. The remaining part of compilation is deferred until the point in time at which the store procedure is executed. This is referred to as Deferred Name Resolution and allows a stored procedure to include references to tables (not just limited to temp tables) that do not exist at the point in time that the procedure is created.
Unfortunately, when it comes to the point in time that the procedure is executed, it needs to be able to compile all of the individual statements, and it's at this time that it will discover that the table exists but that the column doesn't - and so at this time, it will generate an error and refuse to run the procedure.
The T-SQL language is unfortunately a very simplistic compiler, and doesn't take runtime control flow into account when attempting to perform the compilation. It doesn't analyse the control flow or attempt to defer the compilation in conditional paths - it just fails the compilation because the column doesn't (at this time) exist.
Unfortunately, there aren't any mechanisms built in to SQL Server to control this behaviour - this is the behaviour you get, and anything that addresses it is going to be perceived as a workaround - as evidenced already by the (valid) suggestions in the comments - the two main ways to deal with it are to use dynamic SQL or to ensure that the temp table always contains all columns required.
One way to workaround your concerns about maintenance if you go down the "all uses of the temp table should have all columns" is to move the column definitions into a separate stored procedure, that can then augment the temporary table with all of the required columns - something like:
create procedure S_TT_Init
as
alter table #TT add Column1 int not null
alter table #TT add Column2 varchar(9) null
go
create procedure S_TT_Consumer
as
insert into #TT(Column1,Column2) values (9,'abc')
go
create procedure S_TT_User
as
create table #TT (tmp int null)
exec S_TT_Init
insert into #TT(Column1) values (8)
exec S_TT_Consumer
select Column1 from #TT
go
exec S_TT_User
Which produces the output 8 and 9. You'd put your temp table definition in S_TT_Init, S_TT_Consumer is the inner query that multiple stored procedures call, and S_TT_User is an example of one such stored procedure.
Create the table with the column initially. If you're populating the TEMP table with SPROC output just make it an IDENTITY INT (1,1) so the columns line up with your output.
Then drop the column and re-add it as the appropriate data type later on in the SPROC.
The only (or maybe best) way i can thing off beyond dynamic SQL is using checks for database structure.
if exists (Select 1 From tempdb.sys.columns Where object_id=OBJECT_ID('tempdb.dbo.#TTT') and name = 'AvgValue')
begin
--do something AvgValue related
end
maybe create a simple function that takes table name and column or only column if its always #TempTable and retursn 1/0 if the column exists, would be useful in the long run i think
if dbo.TempTableHasField('AvgValue')=1
begin
-- do something AvgValue related
end
EDIT1: Dang, you are right, sorry about that, i was sure i had ... this.... :( let me thing a bit more
I have a table ABC in a database DB. I want to create copies of ABC with names ABC_1, ABC_2, ABC_3 in the same DB. How can I do that using either Management Studio (preferably) or SQL queries ?
This is for SQL Server 2008 R2.
Use SELECT ... INTO:
SELECT *
INTO ABC_1
FROM ABC;
This will create a new table ABC_1 that has the same column structure as ABC and contains the same data. Constraints (e.g. keys, default values), however, are -not- copied.
You can run this query multiple times with a different table name each time.
If you don't need to copy the data, only to create a new empty table with the same column structure, add a WHERE clause with a falsy expression:
SELECT *
INTO ABC_1
FROM ABC
WHERE 1 <> 1;
Copy Schema (Generate DDL) through SSMS UI
In SSMS expand your database in Object Explorer, go to Tables, right click on the table you're interested in and select Script Table As, Create To, New Query Editor Window.
Do a find and replace (CTRL + H) to change the table name (i.e. put ABC in the Find What field and ABC_1 in the Replace With then click OK).
Copy Schema through T-SQL
The other answers showing how to do this by SQL also work well, but the difference with this method is you'll also get any indexes, constraints and triggers.
Copy Data
If you want to include data, after creating this table run the below script to copy all data from ABC (keeping the same ID values if you have an identity field):
set identity_insert ABC_1 on
insert into ABC_1 (column1, column2) select column1, column2 from ABC
set identity_insert ABC_1 off
If you want to duplicate the table with all its constraints & keys follows this below steps:
Open the database in SQL Management Studio.
Right-click on the table that you want to duplicate.
Select Script Table as -> Create to -> New Query Editor Window.
This will generate a script to recreate the table in a new query window.
Change the table name and relative keys & constraints in the script.
Execute the script.
Then for copying the data run this below script:
SET IDENTITY_INSERT DuplicateTable ON
INSERT Into DuplicateTable ([Column1], [Column2], [Column3], [Column4],... )
SELECT [Column1], [Column2], [Column3], [Column4],... FROM MainTable
SET IDENTITY_INSERT DuplicateTable OFF
1st option
select *
into ABC_1
from ABC;
2nd option: use SSIS, that is right click on database in object explorer > all tasks > export data
source and target: your DB
source table: ABC
target table: ABC_1 (table will be created)
This is another option:
select top 0 * into <new_table> from <original_table>
You need to write SSIS to copy the table and its data, constraints and triggers. We have in our organization a software called Kal Admin by kalrom Systems that has a free version for downloading (I think that the copy tables feature is optional)
In Case you want to do it N times (may be not practical in real world), you might try this:
declare #counter int
set #counter = 2
declare #tname NVARCHAR(100)
DECLARE #SQString NVARCHAR(MAX)
while(#counter <= 20) -- duplicate 20 times
begin
SET #tname= concat('Table_',#counter)
SET #SQString = 'select * into ' + #tname + ' from Table_1'
EXEC (#SQString)
set #counter = #counter + 1
End
use sql server manegement studio or netcat and that will be easier to manipulate sql
If I try to execute the following code, I get the errors
Msg 207, Level 16, State 1, Line 3 Invalid column name 'Another'. Msg
207, Level 16, State 1, Line 4 Invalid column name 'Another'.
even though the predicate for both IF statements always evaluates to false.
CREATE TABLE #Foo (Bar INT)
GO
IF (1=0)
BEGIN
SELECT Another FROM #Foo
END
GO
IF (1=0)
BEGIN
ALTER TABLE #Foo ADD Another INT
SELECT Another FROM #Foo
END
GO
DROP TABLE #Foo
This is probably over-simplified for the sake of the example; in reality what I need to do is select the values from a column, but only if the column exists. If it doesn't exist, I don't care about it. In the problem that drove me to ask this question, my predicate was along the lines of EXISTS (SELECT * FROM sys.columns WHERE object_id = #ID AND name = #Name). Is there a way to achieve this without resorting to my arch-enemy Dynamic SQL? I understand that my SQL must always be well-formed (i.e. conform to grammar) - even within a block that's never executed - but I'm flabbergasted that I'm also being forced to make it semantically correct too!
EDIT:
Though I'm not sure the code below adds much to the code above, it's a further example of the problem. In this scenario, I only want to set the value of Definitely (which definitely exists as a column) with the value from Maybe (which maybe exists as a column) if Maybe exists.
IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('dbo.TableName', 'U') AND name = 'Maybe')
BEGIN
UPDATE dbo.TableName SET Definitely = Maybe
END
SQL Server doesn't execute line by line. It isn't procedural like .net or Java code. So there is no "non-executed block"
The batch is compiled in one go. At this point, the column doesn't exist but it knows the table will be. Table does not have a column called "Another". Fail.
Exactly as expected.
Now, what is the real problem you are trying to solve?
Some options:
2 tables or one table with both columns
use Stored procedures to decouple scope
not use temp tables (maybe not needed; it could be your procedural thinking...)
dynamic SQL (from Mitch's deleted answer)
Edit, after comment;
Why not hide schema changes behind a view, rather than changing all code to work with columns that may/may not be there?
You can use EXEC to handle it. It's not really dynamic SQL if the code never actually changes.
For example:
CREATE TABLE dbo.Test (definitely INT NOT NULL)
INSERT INTO dbo.Test (definitely) VALUES (1), (2), (3)
IF EXISTS (SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.Test', 'U') AND
name = 'Maybe')
BEGIN
EXEC('UPDATE dbo.Test SET definitely = maybe')
END
SELECT * FROM dbo.Test
ALTER TABLE dbo.Test ADD maybe INT NOT NULL DEFAULT 999
IF EXISTS (SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.Test', 'U') AND
name = 'Maybe')
BEGIN
EXEC('UPDATE dbo.Test SET definitely = maybe')
END
SELECT * FROM dbo.Test
DROP TABLE dbo.Test
You can also try Martin Smith's "Workaround" using a non-existing table to get "deferred name resolution" for columns.
I had the same issue.
We are creating a script for all changes for years and this is the first time that we have this issue.
I've tried all your answers and didn't find the issue.
In my case it was because of temporary table within the script that I'm using also within a stored procedure, although every sentence has go.
I've found that if I'm adding if exists with drop to the temporary table after the script is using the temporary table, it is working correctly.
Best regards,
Chen
Derived from the answer by #gbn.
What i did to solve the issue was to use 'GO' between the ALTER query and the query that uses the column added by ALTER. This will make the 2 queries to be run as separate batches thereby ensuring your 'Another' column is there before the SELECT query.
I have a SQL script that populates a temp column and then drops the column at the end of the script. The first time it runs, it works fine because the column exists, then it gets dropped. The script breaks the 2nd time because the column no longer exists, even though the IF statement ensures that it won't run again. How do I get around SQL checking for this field?
IF EXISTS (SELECT name FROM syscolumns
WHERE name = 'COLUMN_THAT_NO_LONGER_EXISTS')
BEGIN
INSERT INTO TABLE1
(
COLUMN_THAT_NO_LONGER_EXISTS,
COLUMN_B,
COLUMN_C
)
SELECT 1,2,3 FROM TABLE2
ALTER TABLE TABLE1 DROP COLUMN COLUMN_THAT_NO_LONGER_EXISTS
END
I had a similar problem once and got round it by building all the queries as strings and executing them using the Exec() call. That way the queries (selects, inserts or whatever) don't get parsed till they are executed.
It wasn't pretty or elegant though.
e.g
exec('INSERT INTO TABLE1(COLUMN_THAT_NO_LONGER_EXISTS,COLUMN_B,COLUMN_C) SELECT 1,2,3 FROM TABLE2')
Are you checking the column isnt on another table ? If not you probably to check the table too see if statement below.
If you are already doing that is it running a in a single transaction and not picking up the that dropped column has gone ?
IF Not EXISTS (SELECT name FROM sys.columns
WHERE name = 'COLUMN_THAT_NO_LONGER_EXISTS' and Object_Name(object_id) = 'Table1')
Created a quick script program for this; can you confirm this matches what you are trying to do because in SQL 2007 at least this isnt returning an error. If i create the table and run through with teh alter table to add colc it works; if i then run the if / insert that works even after dropping the table.
create table tblTests
(
TestID int identity (1,1),
TestColA int null,
TestColB int null
)
go -- Ran this on its own
insert into tblTests (TestColA, TestColB)
Select 1,2
go 10
-- Insert some initial data
alter table tblTests
add TestColC Int
go -- alter the table to add new column
-- Run this with column and then after it has removed it
IF EXISTS (SELECT name FROM sys.columns a
WHERE name = 'TestColC' AND
OBJECT_NAME(object_id) = 'tblTests')
Begin
insert into tblTests (TestColA, TestColB, testcolc)
select 1,2,3
alter table tblTests
drop column TestColC
End