What's wrong with this tsql? - tsql

When executing the following script, I get the error:
Msg 207, Level 16, State 1, Line 15 Invalid column name 'b'.
Anyone can explain it please? Thanks.
DROP TABLE ttt;
CREATE TABLE ttt(a nvarchar)
IF NOT EXISTS ( SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.ttt')
AND name = 'b' )
AND EXISTS ( SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.ttt')
AND name = 'a' )
BEGIN
ALTER TABLE [dbo].ttt ADD b NVARCHAR
UPDATE [dbo].ttt
SET b = a
ALTER TABLE [dbo].ttt DROP COLUMN a
END

It's trying to compile all of these statements before it executes the 1st:
ALTER TABLE [dbo].ttt ADD b NVARCHAR
UPDATE [dbo].ttt
SET b = a
ALTER TABLE [dbo].ttt DROP COLUMN a
(In fact, it tries to compile the entire batch, not just these statements, but the point still stands - at the point it's trying to compile the UPDATE, the column does not exist)
When it's trying to compile the UPDATE statement, it consults the table metadata and correctly finds that the column doesn't exist.
Try EXECing the update statement.
EXEC('UPDATE [dbo].ttt
SET b = a');
And also, what Oded says about you probably wanting to specify a size for the column (otherwise, it defaults to the most pointless datatype ever - an nvarchar(1))
This script definitely runs without errors:
CREATE TABLE ttt(a nvarchar)
IF NOT EXISTS ( SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.ttt')
AND name = 'b' )
AND EXISTS ( SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.ttt')
AND name = 'a' )
BEGIN
ALTER TABLE [dbo].ttt ADD b NVARCHAR
EXEC('UPDATE [dbo].ttt
SET b = a');
ALTER TABLE [dbo].ttt DROP COLUMN a
END

If you put the code in a stored procedure it should work. Just make sure the table exists with both columns a and column b when you create the procedure. Then after the procedure has been created you can drop the table and test it.

Related

Invalid column name of temp table

I want to create a procedure in which I insert data into several tables. I need to get the inserted ID's so I create temp table in which I catch them. The problem is that I receive an error "Invalid column name 'app_guid'" and "Invalid column name 'app_nazwa_pliku'" but I create temp tables with such columns. Do you happen to know what's wrong with my code?
create procedure p_paseczek_przenies
as
declare #new_nr_sprawy varchar(50)
if object_id('tempdb..##paseczki') is not null drop table ##paseczki
select
top 1 with ties
s.sp_numer as SprawaGlowna_sp_numer,
s.sp_id as SprawaGlowna_sp_id
,Paseczek.max_ak_id as Paseczek_max_ak_id
,apisp_data_przyjscia
,app_guid
,app_nazwa_pliku
into ##paseczki
from sprawa as s
join akcja as a on a.ak_sp_id=s.sp_id and ak_akt_id=111
join sprawa_powiazania as sp on s.sp_id=sp.sp_id and rodzaj_powiazania='SPRAWY POLUBOWNE'
join (select max(ak_id) max_ak_id,ak_sp_id from akcja
where ak_akt_id=1089
group by ak_sp_id) as Paseczek on Paseczek.ak_sp_id=sp.sp_id_powiazana
join akcja_pismo on apis_ak_id=max_ak_id
join akcja_pismo_przychodzace on apis_apisp_id=apisp_id
join akcja_pismo_plik on app_apis_id=apis_id
where s.sp_numer=#new_nr_sprawy
order by ROW_NUMBER() over (partition by s.sp_id order by paseczek.max_ak_id desc)
if exists (select * from ##paseczki)
begin
if object_id('tempdb..##akcja') is not null drop table ##akcja
create table ##akcja (
ak_id int
,apisp_data_przyjscia datetime
,app_guid varchar(max)
,app_nazwa_pliku varchar(max)
)
merge akcja as target using (
select * from ##paseczki) as source on 1=0
when not matched then insert
(ak_akt_id, ak_sp_id, ak_kolejnosc, ak_interwal, ak_zakonczono, ak_pr_id, ak_publiczna)
values (1089,SprawaGlowna_sp_id,1,1,getdate(),5,1)
output inserted.ak_id,source.apisp_data_przyjscia,source.app_guid,source.app_nazwa_pliku
into ##akcja;
insert into rezultat
(re_ak_id, re_ret_id, re_data_planowana, re_us_id_planujacy, re_data_wykonania, re_us_id_wykonujacy, re_konczy)
select ak_id,309,getdate(),5,getdate(),5,1 from ##akcja
if object_id('tempdb..##akcja_pismo_przychodzace') is not null drop table ##akcja_pismo_przychodzace
create table ##akcja_pismo_przychodzace (
apisp_id int
,ak_id int
,app_guid varchar(max)
,app_nazwa_pliku varchar(max)
)
merge akcja_pismo_przychodzace as target using (
select * from ##akcja) as source on 1=0
when not matched then insert
(apisp_data_przyjscia)
values (apisp_data_przyjscia)
output inserted.apisp_id,source.ak_id,source.app_guid,source.app_nazwa_pliku
into ##akcja_pismo_przychodzace;
if object_id('tempdb..##akcja_pismo') is not null drop table ##akcja_pismo
create table ##akcja_pismo (
apis_id int
,app_guid varchar(max)
,app_nazwa_pliku varchar(max)
)
merge akcja_pismo as target using (
select * from ##akcja_pismo_przychodzace) as source on 1=0
when not matched then insert
(apis_ak_id, apis_apisp_id, apis_data_stworzenia,[apis_us_id_tworzacy])
values (ak_id,apisp_id,getdate(),5)
output inserted.apis_id,source.app_guid,source.app_nazwa_pliku
into ##akcja_pismo;
alter table [dm_data_bps].[dbo].[akcja_pismo_plik] disable trigger [tr_akcja_pismo_plik_ins]
insert into akcja_pismo_plik
([app_guid],[app_apis_id],[app_nazwa_pliku])
select [app_guid],[apis_id],[app_nazwa_pliku] from ##akcja_pismo
alter table [dm_data_bps].[dbo].[akcja_pismo_plik] enable trigger [tr_akcja_pismo_plik_ins]
end
SQL Server compiles the procedure at creation and when it is first executed, verifying the entire procedure based on the context at that time.
For example, try the following query:
CREATE PROCEDURE P
AS
IF OBJECT_ID('tempdb..#T') IS NOT NULL DROP TABLE #T
SELECT 1 Y INTO #T
SELECT Y FROM #T
GO
CREATE TABLE #T (X INT)
GO
EXEC P
You will get an error ("Invalid column name 'Y'."), because when the procedure is compiled the table #T has only the column X.
To avoid this problem, you should make sure that the table #T either does not exist or has the right columns, before the procedure is executed.
One way would be to have another stored procedure (a wrapper):
CREATE PROCEDURE P1
AS
SELECT 1 Y INTO #T
SELECT Y FROM #T
GO
CREATE PROCEDURE P2
AS
IF OBJECT_ID('tempdb..#T') IS NOT NULL DROP TABLE #T
EXEC P1
GO
CREATE TABLE #T (X INT)
GO
EXEC P2
GO
DROP PROCEDURE P1, P2
--DROP TABLE #T
Another way would be to use dynamic SQL, because that code is compiled separately, as if it would be another stored procedure.
A better way would be to make sure that temp tables are uniquely named in each stored procedure, unless sharing data between them is desired. For the later case, you can read http://www.sommarskog.se/share_data.html#temptables for more insights.
This error is also encountered when a stored procedure creates a #temp table and then fires a trigger which creates a #temp table with the same name. The SP #temp table is referenced by the trigger when the column names are explicit, (like SELECT id FROM #temp;), but the local trigger #temp table is referenced when SELECT * FROM #temp; is used.
Microsoft, if you are listening, could you kindly attend to it and retrofit existing supported versions with a maintenance update?

Make duplicate row in Postgresql

I am writing migration script to migrate database. I have to duplicate the row by incrementing primary key considering that different database can have n number of different columns in the table. I can't write each and every column in query. If i simply just copy the row then, I am getting duplicate key error.
Query: INSERT INTO table_name SELECT * FROM table_name WHERE id=255;
ERROR: duplicate key value violates unique constraint "table_name_pkey"
DETAIL: Key (id)=(255) already exist
Here, It's good that I don't have to mention all column names. I can select all columns by giving *. But, same time I am also getting duplicate key error.
What's the solution of this problem? Any help would be appreciated. Thanks in advance.
If you are willing to type all column names, you may write
INSERT INTO table_name (
pri_key
,col2
,col3
)
SELECT (
SELECT MAX(pri_key) + 1
FROM table_name
)
,col2
,col3
FROM table_name
WHERE id = 255;
Other option (without typing all columns , but you know the primary key ) is to CREATE a temp table, update it and re-insert within a transaction.
BEGIN;
CREATE TEMP TABLE temp_tab ON COMMIT DROP AS SELECT * FROM table_name WHERE id=255;
UPDATE temp_tab SET pri_key_col = ( select MAX(pri_key_col) + 1 FROM table_name );
INSERT INTO table_name select * FROM temp_tab;
COMMIT;
This is just a DO block but you could create a function that takes things like the table name etc as parameters.
Setup:
CREATE TABLE public.t1 (a TEXT, b TEXT, c TEXT, id SERIAL PRIMARY KEY, e TEXT, f TEXT);
INSERT INTO public.t1 (e) VALUES ('x'), ('y'), ('z');
Code to duplicate values without the primary key column:
DO $$
DECLARE
_table_schema TEXT := 'public';
_table_name TEXT := 't1';
_pk_column_name TEXT := 'id';
_columns TEXT;
BEGIN
SELECT STRING_AGG(column_name, ',')
INTO _columns
FROM information_schema.columns
WHERE table_name = _table_name
AND table_schema = _table_schema
AND column_name <> _pk_column_name;
EXECUTE FORMAT('INSERT INTO %1$s.%2$s (%3$s) SELECT %3$s FROM %1$s.%2$s', _table_schema, _table_name, _columns);
END $$
The query it creates and runs is: INSERT INTO public.t1 (a,b,c,e,f) SELECT a,b,c,e,f FROM public.t1. It's selected all the columns apart from the PK one. You could put this code in a function and use it for any table you wanted, or just use it like this and edit it for whatever table.

IF Exists doesn't seem to work for a Table Drop if already exists

Was getting this error each and every time tried to execute a DROP Table if exists
Step 1: Created a Table
CREATE TABLE Work_Tables.dbo.Drop_Table_Test (RowID INT IDENTITY(1,1), Data VARCHAR(50))
INSERT INTO Work_Tables.dbo.Drop_Table_Test
SELECT 'Test' UNION
SELECT 'Test1' UNION
SELECT 'Test2' UNION
SELECT 'Test3'
Step 2: Wrote a IF Exists block to check if the Table exists.
IF EXISTS (SELECT 1 FROM Work_Tables.dbo.SysObjects WHERE NAME LIKE 'Drop_Table_Test' AND XType = 'U')
BEGIN
PRINT 'IN'
DROP TABLE Work_Tables.dbo.Drop_Table_Test
END
CREATE TABLE Work_Tables.dbo.Drop_Table_Test (RowID INT IDENTITY(1,1), Data VARCHAR(50), NAME VARCHAR(20), PreCheck INT)
INSERT INTO Work_Tables.dbo.Drop_Table_Test (Data, Name, PreCheck)
SELECT 'Test','SRK',1 UNION
SELECT 'Test1','Daya',2 UNION
SELECT 'Test2','Dinesh',3 UNION
SELECT 'Test3','Suresh',4
On running the Step 2 Code its obvious the Table has to be Dropped and recreated with the same name but it didn't even enter the Begin End block.
I feel that its because have added few more columns in the second try, but still not clear why it should have problems as we are to DROP the table.
You can not drop and create the same table in the same batch in SQL Server.
Break your code up into separate batches so the table can be dropped before you try and recreate it. Add GO after END in your BEGIN / END statement.
IF EXISTS (SELECT 1 FROM Work_Tables.dbo.SysObjects WHERE NAME LIKE 'Drop_Table_Test' AND XType = 'U')
BEGIN
PRINT 'IN'
DROP TABLE Work_Tables.dbo.Drop_Table_Test
END
GO --Add this...
....
Straight from Microsoft's Documentation:
DROP TABLE and CREATE TABLE should not be executed on the same table in the same batch. Otherwise an unexpected error may occur.
You can try to use this syntax:
IF OBJECT_ID('dbo.Drop_Table_Test', 'U') IS NOT NULL
DROP TABLE dbo.Drop_Table_Test;
IF EXISTS will drop the table only when your table Drop_Table_Test does not contain any row. In case if it contains the data then it will not drop the table.

Using a CASE Expression in the Default Value

I am creating a script for my dba to run. In development I have added a new column to an existing table. When I run the following script I have separated the ALTER and UPDATE on my table. I was trying to combine the statements into one. Any suggestions? Not sure if this is possible or I am on the right track. Thanks in advance!
Current Script that is working fine:
if not exists(select * from sys.columns where Name = N'NewColumn' and
Object_ID = Object_ID(N'TableA'))
begin
ALTER TABLE dbo.TableA
ADD NewColumn BIT NULL
DEFAULT 0
end
if exists(select * from sys.columns where Name = N'NewColumn' and Object_ID = Object_ID(N'TableA'))
begin
UPDATE dbo.TableA
SET Newcolumn = CASE WHEN CodeID IN ('A','B') THEN 1 END
WHERE CodeID IN ('A','B')
end
Trying to combine the ALTER and UPDATE into one:
if not exists(select * from sys.columns where Name = N'NewColumn' and Object_ID = Object_ID(N'TableA'))
begin
ALTER TABLE dbo.TableA
ADD NewColumn BIT NULL
DEFAULT case CodeID
WHEN 'A' THEN 1
WHEN 'B' THEN 1
end
I get the following error:
The name "CodeID" is not permitted in this context. Valid expressions are constants, constant expressions, and (in some contexts) variables. Column names are not permitted.
I haven't actually tested this, but I noticed you used different syntax for the CASE statement in the working script and the non-working script. Try this:
if not exists(select * from sys.columns where Name = N'NewColumn' and Object_ID = Object_ID(N'TableA'))
begin
ALTER TABLE dbo.TableA
ADD NewColumn BIT NULL
DEFAULT case WHEN CodeID = 'A' THEN 1 ELSE 0 END
end
But it would not surprise me if you can't reference another column in the default for a column. For example, what happens when you insert a new record without specifying a value for NewColumn? At insert time, CodeID doesn't have a value because the row doesn't exist yet.
Ok, so i did some more digging around. The following solves my problem:
if not exists(select * from sys.columns where Name = N'NewColumn' and
Object_ID = Object_ID(N'TableA'))
begin
ALTER TABLE dbo.TableA
ADD NewColumn BIT NULL
DEFAULT 0 with Values
end
if exists(select * from sys.columns where Name = N'NewColumn' and Object_ID = Object_ID(N'TableA'))
begin
UPDATE dbo.TableA
SET Newcolumn = 1 WHERE CodeID IN ('A','B')
end
I was over complicating things. In order to combine the update I would have to try and add a trigger. This works for what I need to do. Thanks.

How to create a table with a name from a variable

I'm using the following syntax for all the tables I'm creating with a script.
if ((select count(*) from sys.tables where Name = 'SpecificName') > 0)
drop table SpecificName
create table SpecificName( ... )
Then, I got lazy of editing three places each time I add a table, so I tried the following.
declare #name varchar(100) = 'NameToBe'
if ((select count(*) from sys.tables where Name = #name) > 0)
drop table #name
create table #name( ... )
Of course, this doesn't work because the server won't allow me for dropping nor creating the tables referred to by a string. Can this be done and if so how?