This question already has answers here:
How to use a variable for the database name in T-SQL?
(4 answers)
Closed 2 years ago.
I have a problem with assigning a database name to a variable. When I do this, I get a message that there is no such database.
What am I doing wrong that this script doesn't work?
DECLARE #db_name varchar(10)
SET #db_name = 'xxx'
ALTER TABLE [#db_name].[dbo].[Table_Name]
DROP CONSTRAINT [Constraint_Name]
GO
ALTER TABLE [#db_name].[dbo].[Table_Name] WITH CHECK
ADD CONSTRAINT [Constraint_Name] CHECK (QUERY)
GO
ALTER TABLE [#db_name].[dbo].[Table_Name] CHECK CONSTRAINT [Constraint_Name]
GO
I'm getting this error message:
Msg 2702, Level 16, State 2, Line 5
Database '#db_name' does not exist.
Msg 2702, Level 16, State 2, Line 8
Database '#db_name' does not exist.
Msg 2702, Level 16, State 2, Line 11
Database '#db_name' does not exist.
let's say for example that you have a table created like this:
create table xxx(id int
CONSTRAINT AK_TransactionID UNIQUE(id))
Then you will need to cerate two variables(one for the object name and one for the query to be executed...):
DECLARE #db_name varchar(10) ;
DECLARE #query nvarchar(500) ;
Set them to the values you need:
SET #db_name = 'xxx';
set #query = 'ALTER TABLE ['+ #db_name +' ] DROP CONSTRAINT [AK_TransactionID]';
And then execute the query:
EXEC sp_executesql #query;
Here is a demo
A query - meaning all text until each GO command - is compiled before it is run.
Database names inside queries have to be be known at compile time, because the database + any table or column you specify are inspected by the query compiler to see if they are valid.
This means that Database names + Table names + Column names all cannot come from #-variables, because at compile time variables don't yet exist and have no value.
The fix is to use dynamic SQL:
DECLARE #db_name varchar(10) = 'xxx'
DECLARE #sql varchar(1000) = 'ALTER TABLE [' + #db_name + '].[dbo].[Table_Name] DROP CONSTRAINT [Constraint_Name]'
EXEC (#sql)
Unfortunately in SQL Server, you cannot pass schema, database, table or column names as a parameter - if you want to do this, you must use dynamic SQL. You could do the below to achieve this:
DECLARE #db_name VARCHAR(MAX)
DECLARE #SQL VARCHAR(MAX)
SET #db_name = [xxx]
SET #SQL = 'ALTER TABLE' + #db_name+ '.[dbo].[Table_Name] DROP CONSTRAINT [Constraint_Name]'
EXEC (#SQL)
SET #SQL = 'ALTER TABLE' + #db_name + '.[dbo].[Table_Name] WITH CHECK ADD CONSTRAINT [Constraint_Name] CHECK (QUERY)'
EXEC (#SQL)
SET #SQL = 'ALTER TABLE' + #db_name + '.[dbo].[Table_Name] CHECK CONSTRAINT [Constraint_Name]'
EXEC (#SQL)
Related
I've created a column called "Identified" and I've assigned a default value of "1". Now I realized that I would like to delete that column. But when I try I get the following error:
Msg 5074, Level 16, State 1, Line 5
The object 'DF__orders__Identifi__403A8C7D' is dependent on column 'Identified'.
Msg 4922, Level 16, State 9, Line 5
ALTER TABLE DROP COLUMN Identified failed because one or more objects access this column.
Here is the code used:
ALTER TABLE BikeStores.sales.orders
DROP COLUMN Identified;
I've also tried:
ALTER TABLE BikeStores.sales.orders
DROP Identified;
But in this case the error get is:
Msg 3728, Level 16, State 1, Line 5
'Identified' is not a constraint.
Msg 3727, Level 16, State 0, Line 5
Could not drop constraint. See previous errors.
Any tip on how to fix this error? Thanks in advance
The column cannot be dropped because the default constraint is dependant on the column existing. Drop the default constraint first.
If the version of SQL server you are working with is 2016 or before you can use the simple query:
ALTER TABLE BikeStores.sales.orders ALTER COLUMN Identified DROP DEFAULT;
From 2017 onwards you will need to explicitly use the default constraints name
ALTER TABLE BikeStores.sales.orders DROP CONSTRAINT DF__orders__Identifi__403A8C7D;
If you do not know the name of the default (due to it being auto named on creation) the following script will identify and remove the default.
declare #schema_name nvarchar(256)
declare #table_name nvarchar(256)
declare #col_name nvarchar(256)
declare #Command nvarchar(1000)
set #schema_name = N'sales'
set #table_name = N'orders'
set #col_name = N'Identitified'
select #Command = 'ALTER TABLE ' + #schema_name + '.[' + #table_name + '] DROP CONSTRAINT ' + d.name
from sys.tables t
join sys.default_constraints d on d.parent_object_id = t.object_id
join sys.columns c on c.object_id = t.object_id and c.column_id = d.parent_column_id
where t.name = #table_name
and t.schema_id = schema_id(#schema_name)
and c.name = #col_name
EXEC sp_executesql #Command
(credit to this question for the last part ) How to drop SQL default constraint without knowing its name?
You will then be able to drop your column
I created procedure which count not null rows in the column, but query throws errors: #tableName is not declared and invalid object name tempTable. I don't know why code throws that errors, because all variables are declared.
Msg 1087, Level 16, State 1, Procedure getLenCol, Line 7 [Batch Start Line 0]
Must declare the table variable "#tableName".
Msg 208, Level 16, State 1, Line 11
Invalid object name 'tempTable'.
CREATE OR ALTER PROC getLenCol
#tableName varchar(255),
#colName varchar(255)
as
DECLARE #tempTable Table(smth varchar(255));
DECLARE #query varchar(255)
insert into #tempTable(smth) select #colName from #tableName where #colName is not null
exec (#query)
select ##ROWCOUNT
GO
exec getLenCol 'users','name'
Also when I make that program in another way, that code throw
Msg 1087, Level 15, State 2, Line 11
error.
Must declare the table variable "#tempTable".
CREATE OR ALTER PROC getLenCol
#tableName varchar(255),
#colName varchar(255)
as
DECLARE #tempTable Table(smth varchar(255))
DECLARE #query varchar(255)
SET #query = concat('insert into #tempTable(smth) select ',#colName,' from ',#tableName,' where ',#colName,' is not null');/*#colName from #tableName where #colName is not NULL*/
exec (#query)
select ##ROWCOUNT
GO
exec getLenCol 'users','name'
Is it a way to fix that error?
Obviously, your code is subject to SQL injection attacks -- as the comments on the question have explained.
But your issue is the scoping rules around your table variable. You can fix that by using:
set #query = concat('select ', #colName, ' from ', #tableName, ' where ', #colName,' is not null');
insert into #tempTable (smth)
exec(#query);
I don't think there is any way around the SQL injection vulnerabilities for the logic you have suggested. However, your code is so non-sensical that I doubt that it is really representative of your actual code.
As it seems that many are not aware of the dangers of SQL Injection, including Gordon, I wanted to expand on that first. Let's, take the accepted answer (at time of writing), which gives the following:
CREATE OR ALTER PROC getLenCol
#tableName varchar(255),
#colName varchar(255)
as
DECLARE #query varchar(255)
DECLARE #tempTable Table(smth varchar(255))
set #query = concat('select ', #colName, ' from ', #tableName, ' where ', #colName,' is not null');
insert into #tempTable (smth)
exec(#query);
GO
Now, let's be someone malicious:
EXEC dbo.getLenCol #colName = N'1; CREATE LOGIN NewLogin WITH PASSWORD = ''1'', CHECK_POLICY = OFF;/*',
#tableName =N'*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--';
So, what does the above, in the dynamic SQL run? Let's find out by adding PRINT #query; to the SP's definition:
select 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* from */ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;-- where 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* is not null
And, with a little formatting for ease of reading:
select 1;
CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;
/* from */
ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;
-- where 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* is not null
OH. OHHHHHHHHHHH. Congratulations you are the new proud owner of a SQL Server that has a new sysadmin LOGIN!
NEVER, inject unsanitised string into a string in SQL. NEVER.
Rather than repeating myself, I'm going to link to my article Dos and Don'ts of Dynamic SQL, however, you can easily make the above query secure with a few of uses of QUOTENAME:
CREATE OR ALTER PROC getLenCol
#schemaName sysname = N'dbo', --You should define the schema too
#tableName sysname, --An object can't be longer than 128 characters, so sysname is best
#colName sysname
AS
BEGIN
DECLARE #query nvarchar(MAX);
DECLARE #tempTable Table(smth varchar(255));
SET #QUERY = CONCAT(N'SELECT ', QUOTENAME(#colName),N' FROM ', QUOTENAME(#schemaName), N'.', QUOTENAME(#tableName), N' WHERE ', QUOTENAME(#colName), N' IS NOT NULL;');
PRINT #query;
INSERT INTO #tempTable (smth)
EXEC sys.sp_executesql #query;
END;
GO
And what happens if we run the above EXEC statement before? Well you get the statement below (with added formatting):
SELECT [1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/*]
FROM [dbo].[*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--]
WHERE [1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/*] IS NOT NULL;
And no surprised, that generated the error
Invalid object name 'dbo.*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--'.
Now your dynamic statement is safe from injection.
I would highly recommend against this approach, firstly calling this procedure is as much, if not more typing that just doing a count. Compare the two
EXECUTE dbo.getLenCol #tableName = 'dbo.SomeTable', #colName = 'ID';
SELECT COUNT(ID) FROM dbo.SomeTable;
Even with the shortened exec, and not using named parameters it is longer:
EXEC dbo.getLenCol dbo.SomeTable', 'ID';
It is very, very rare that a catch all query like this, with object names being passed as parameters is going to be the correct approach. There are some maintenance queries where it is useful, but these are the exception, not the rule.
If you must do this though, you should do a little bit of validation first, and check that both the table name and column name are valid before executing any dynamic SQL by using COL_LENGTH(#tableName, #ColName). e.g
CREATE OR ALTER PROC getLenCol #tableName SYSNAME, #colName SYSNAME
AS
BEGIN
IF COL_LENGTH(#tableName, #ColName) IS NOT NULL
BEGIN
DECLARE #SQL NVARCHAR(MAX) = CONCAT('SELECT COUNT(', #colName, ') FROM ', #tableName, ';');
EXECUTE sp_executesql #SQL;
RETURN;
END
-- TABLE OR COLUMN WAS NOT VALID RETURN -1 TO INDICATE THAT
SELECT -1;
END
i have created the table called test_exam
create table test_exam(id int,age int)
than i want through procedure add column called name and update it value.
i have made the procedure
create procedure [dbo].[abcd1] #tablename sysname
as
begin
declare #query nvarchar(4000)
declare #name_test varchar(20)
set #query = N'select top 10 * from transorg_DW.dbo.'+#tablename
print #query
alter table test_exam add name varchar(20);
update test_exam set name='shyam' where id=1;
exec sp_executesql
end
its through an error
invalid column name.
how to rectify this error?
You should call it dynamically and in separate, I think, it will for the column at the compile time so it is displaying error. For now you can do the following
DECLARE #sql NVARCHAR(500)
SET #sql = ' alter table purchase add stat int'
EXEC sp_executesql #sql
SET #sql = ' update purchase set stat=0'
EXEC sp_executesql #sql
Note: Please check the column existence if you would need to call multiple times
When you add any column to existing table then allow null or provide some default value.
And also prevent the DDL command from being executed more then once, otherwise it will throw error on next execution.
And for update its throw error because name column didn't exist while creating the proc. Update statement must as dynamic query.
create procedure [dbo].[abcd1] #tablename sysname
as
begin
declare #query nvarchar(4000)
declare #name_test varchar(20)
set #query = N'select top 10 * from transorg_DW.dbo.'+#tablename
print #query
if not exists(select * from sys.columns c join sys.tables t on c.object_id=t.object_id
where t.name='test_exam' and c.name='name')
begin
--new column added must be as null value or default value
alter table test_exam
add name varchar(20) null;
end
declare #query1 nvarchar(4000)
set #query1='update test_exam set name=''shyam'' where id=1';
exec (#query1)
exec sp_executesql
end
Enjoy hope this will help.
I want to use a stored procedure to copy a table from my test database to a linked server with the same ID's / Identity but I can't get it to work..
I've set the IDENTITY_INSERT to ON but it still complains about the ID column.
Here's my procedure:
CREATE PROCEDURE [dbo].[TEST2PROD_CopyUIDataSServer]
AS Begin
declare #sql nvarchar(max)
-- First truncate target table
set #sql = 'EXEC [LINKEDSERVER].tempdb.sys.sp_sqlexec' + char(39)+ 'TRUNCATE Table [ProductManager].dbo.[UIData]' + char(39)+ ';'
---- SET IDENTITY_INSERT ON
set #sql = #sql + 'EXEC [LINKEDSERVER].tempdb.sys.sp_sqlexec' + char(39)+ 'SET IDENTITY_INSERT [ProductManager].[dbo].[UIData] ON' + char(39)+ ';'
---- INSERT UIDATA records from DB1 into linked server DB2
set #sql = #sql + 'WITH TestData as (SELECT * from ProductManager.dbo.UIData UID)' + NCHAR(13)+ 'INSERT INTO [LINKEDSERVER].[ProductManager].[dbo].[UIData]' + NCHAR(13) + 'select * from TestData;'
print #sql
exec (#sql)
end
But when I execute the SP it gives me the following error:
The OLE DB provider "SQLNCLI10" for linked server .... could not INSERT INTO table "[LINKEDSERVER].[ProductManager].[dbo].[UIData]" because of column "Id". The user did not have permission to write to the column.
Linked server properties RPC and RPC out are set to true. I hope someboy can help me out here?
UPDATE: I decided to pull things apart, first I copy the data from the local server to the linked server in a TEMP_TABLE where I don't have to deal with IDENTITY issues.
Then I wrote a stored procedure on the linked / remote server, since I'm not using SELECT * but specify the column list. Chances are this will work from the local server in an SP too but I don't have the time or interest to check it out yet..
USE [ProductManager]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[TEST2PROD_CopyBaseTables]
AS BEGIN
DECLARE #DestTable VARCHAR(50)
DECLARE #DestPath VARCHAR(50)
DECLARE #SrceTable VARCHAR(255)
declare #sql nvarchar(max)
DECLARE #columnList varchar(max)
DECLARE #err int
Begin TRY
declare #comma_delimited_list varchar(4000)
--- FIRST TRY WITH ONE TABLE, EXTENDABLE...
set #comma_delimited_list = 'UIData'
declare #cursor cursor
set #cursor = cursor static for
select * from dbo.Split(#comma_delimited_list,',') a
declare #naam varchar(50)
open #cursor
while 1=1 begin
fetch next from #cursor into #DestTable
if ##fetch_status <> 0 break
--Create tablenames
SET #SrceTable = '[ProductManager].[dbo].TEMP_' + #DestTable
SET #DestPath = '[ProductManager].[dbo].'+ #DestTable
print #srceTable;
print #DestTable;
--Truncate target table
set #sql ='TRUNCATE TABLE '+ #DestPath + ';'
--Insert statement needs column names
set #columnList =''
SELECT #columnList = coalesce(#columnList + '[' + name + '],','') FROM sys.columns Where OBJECT_NAME(OBJECT_ID) = #DestTable
if RIGHT(RTRIM(#columnList),1) = ','
begin
SET #columnList = LEFT(#columnList, LEN(#columnList) - 1)
end
--Transfer data from source table 2 destination
set #sql = #sql + ' SET IDENTITY_INSERT ' + #DestPath + ' ON;' + ' INSERT INTO ' + #DestPath + '(' + #columnList + ') SELECT ' + #columnList + ' FROM ' + #SrceTable
print #sql;
exec (#sql)
end
-- not strictly necessary w/ cursor variables since the will go out of scope like a normal var
close #cursor
deallocate #cursor
End Try
Begin Catch
declare #ErrorMsg nvarchar(MAX);
select #ErrorMsg = ERROR_MESSAGE();
SELECT #err = ##error IF #err <> 0 Return #err
end Catch
END
IDENTITY_INSERT doesn't work with linked servers AFAIK, unless you execute dynamic SQL that includes the SET IDENTITY_INSERT in the batch or have some code (Stored Proc for instance) on the remote server which does that for you.
The IDENTITY_INSERT is per-session (see MSDN) and when you use the remote server this will probably be in a different session from your statement executed via [LINKEDSERVER].tempdb.sys.sp_sqlexec, which causes it to fail as you see it happening.
You can insert an identity value into a table with an identity column on a linked server with the "SWITCH TO" trick.
If you haven't used the "SWITCH TO" trick to add and remove identity on a column, it's very quick, even on large tables!
Conceptually you simply create a new SCHEMA exactly like the table you are wanting to INSERT to without the identity defined. Then switch the table to that SCHEMA and do your INSERT. Then switch back to the SCHEMA with the identity defined.
The sample below has been tested on a linked server in AZURE.
All the caveats of using "SWITCH TO" apply (indexes must be the same, drop and recreate foreign keys, etc)
To test, you can run the full script below on an Linked Azure SQL Server database. You'll need to do a find/replace with [LINKED_SERVER_NAME] and [DATABASE_NAME], replacing with your values. On a non-Azure DB you may need to add "ON PRIMARY" to the table creations.
--Let's setup the example by creating a table with an IDENTITY column on the Linked Server
EXEC('
CREATE TABLE [DATABASE_NAME].[dbo].[Example_Table](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nchar](10) NULL
)
'
) AT [LINKED_SERVER_NAME]
--INSERT some data into the table
INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] ([Name]) VALUES ('Travis')
INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] ([Name]) VALUES ('Mike')
-- Looks good
SELECT * FROM [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table]
GO
-- Create a TABLE with an identical schema, without the identity defined
EXEC('
CREATE TABLE [DATABASE_NAME].[dbo].[Example_Table_temp](
[ID] [int] NOT NULL,
[Name] [nchar](10) NULL
)
'
) AT [LINKED_SERVER_NAME]
--Now Use the "SWITCH TO" to move the data to the new table
EXEC('
ALTER TABLE [DATABASE_NAME].[dbo].[Example_Table] SWITCH TO [DATABASE_NAME].[dbo].[Example_Table_temp]
'
) AT [LINKED_SERVER_NAME]
--Drop the old table (It should now be empty, but you may want to verify that if you are unsure here)
EXEC('
DROP TABLE [DATABASE_NAME].[dbo].[Example_Table]
'
) AT [LINKED_SERVER_NAME]
--Rename the new table back to the old table name
-- NOTE the lack of database and owner identifiers in the new name
-- NOTE the use of double single qoutes (ESCAPED single quotes)
EXEC('USE [DATABASE_NAME];
EXEC sp_rename ''[DATABASE_NAME].[dbo].Example_Table_temp'',''Example_Table''
'
) AT [LINKED_SERVER_NAME]
-- Now do your IDENTITY INSERTs !!!!
INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] (ID,[Name]) VALUES (888,'Travis')
INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] (ID,[Name]) VALUES (999,'Mike')
--Verify they got put in
SELECT * FROM [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table]
--Now let's switch it back to our SCHEMA with an IDENTITY
EXEC('
CREATE TABLE [DATABASE_NAME].[dbo].[Example_Table_temp](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nchar](10) NULL
)
ALTER TABLE [DATABASE_NAME].[dbo].[Example_Table] SWITCH TO [DATABASE_NAME].[dbo].[Example_Table_temp]
DROP TABLE [DATABASE_NAME].[dbo].[Example_Table]
EXEC sp_rename ''[DATABASE_NAME].[dbo].Example_Table_temp'',''Example_Table''
'
) AT [LINKED_SERVER_NAME]
--Data is still there
SELECT * FROM [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table]
GO
-- And note you can no longer INSERT the IDENTITY
INSERT INTO [LINKED_SERVER_NAME].[DATABASE_NAME].[dbo].[Example_Table] (ID,[Name]) VALUES (45,'Travis')
GO
You need to execute dynamic query Example :
exec [LINKSERVERNAME].[DATABASENAME].[SCHEMANAME].sp_executesql N'Your Query'
If any column set identity the you need to set SET IDENTITY_INSERT TargetTable ON and need to specified the column name. Example:
SET IDENTITY_INSERT TargetTable ON;
INSERT INTO TargetTable(Col1, Col2, Col3)
SELECT Col1, Col2, Col3 FROM SourceTable;
SET IDENTITY_INSERT TargetTable OFF;
I am trying to alter database through a DDL trigger which will fire on creation. However I am getting a below error.
CREATE TRIGGER ddl_trig_database
ON ALL SERVER
FOR CREATE_DATABASE
AS
declare #dbname as nvarchar(100)
declare #sql as nvarchar(max)
select #dbname =
CAST(eventdata().query(
'/EVENT_INSTANCE/DatabaseName[1]/text()'
) as NVarchar(128))
select #sql = N'SET IMPLICIT_TRANSACTIONS OFF
ALTER DATABASE ' + #dbname+ N' SET COMPATIBILITY_LEVEL = 110
SET IMPLICIT_TRANSACTIONS ON'
exec (#sql)
GO
create database test
Error:
Msg 226, Level 16, State 6, Line 22
ALTER DATABASE statement not allowed within multi-statement transaction.
The statement has been terminated.
I am on SQL Server 2014 on Windows 2012.
If you want a specific compatibility level for each new database created - just set that compatibility level in the model database which is the "template" for all new databases being created ...
No need for a system-level trigger for this ....
I realized the DDL trigger will be on its own transaction and Alter is not allowed if a transaction is already started. So to workaround with this problem I have created SQL Job. and put the Alters in the Job and modified the Trigger to call msdb..start_sql_job.
--Trigger
CREATE TRIGGER ddl_trig_database
ON ALL SERVER
FOR CREATE_DATABASE
AS
exec msdb..sp_start_job 'Initialize Database'
GO
--Job
declare #dbname as nvarchar(100)
declare #sql as nvarchar(max)
select top 1 #dbname = name from sys.databases
where name like 'gtp%' and create_date >= getdate() - .08
order by create_date desc
IF #dbname is not null
begin
select #sql = N'ALTER DATABASE ' + #dbname+ N' SET COMPATIBILITY_LEVEL = 110'
exec sp_executesql #sql
print 'Altered database'
end
print 'completed'