Stored procedure to drop table - sql

I have created a stored procedure that will drop a table if it exists in a database. When running the stored procedure with EXEC, I am getting the following error:
Msg 203, Level 16, State 2, Procedure
sp_DropIfExists, Line 13 The name 'IF
EXISTS(SELECT 1 FROM sys.objects WHERE
OBJECT_ID = OBJECT_ID(N'table_name')
AND type = (N'U')) DROP TABLE
[table_name]' is not a valid
identifier.
However if i copy and paste the T-SQL that is generated into management studio, it seems to be running fine. Can someone explain why this is not valid? The fix would be nice, but I am really after the Why primarily, The How would be nice to though! Thanks in advance.
ALTER PROCEDURE [dbo].[sp_DropIfExists](#tableName VARCHAR(255))
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL VARCHAR(MAX);
SET #SQL = 'IF EXISTS(SELECT 1 FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N''' + #tableName + ''') AND type = (N''U'')) DROP TABLE [' + #tableName + ']'
PRINT #SQL;
EXEC #SQL;
END

you can use sp_execute
sp_executesql #SQL
for more information msdn document link

Not sure if this will solve your problems but you would be better placing you check is a function like so
CREATE FUNCTION [dbo].[TableExists]
(
#TableName VarChar(100)
)
RETURNS BIT
AS
BEGIN
DECLARE #TableExists BIT
IF EXISTS(SELECT name FROM sysobjects a
WHERE a.name = #TableName
AND a.xtype = 'U')
SET #TableExists = 1
ELSE
SET #TableExists = 0
RETURN #TableExists
END
Then you can use it as follows.
IF dbo.TableExists('[table_name]') = 1
DROP TABLE [table_name]
Try this and let me know if you still get the same error.

--ALTER (if procedure exists)
CREATE PROCEDURE sp_dropifexists (#tableName VARCHAR(255))
AS
BEGIN
DECLARE #SQL VARCHAR(MAX);
SET #SQL = 'IF EXISTS(SELECT 1 FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N''' + #tableName + ''') AND type = (N''U'')) DROP TABLE [' + #tableName + ']'
--if write EXEC #SQL without parentheses sql says Error: is not a valid identifier
EXEC (#SQL);
END
--test procedure
exec sp_DropIfExists 'table'

EXEC #SQL should be EXEC (#SQL). (But #maycil's suggestion is correct too.)
Turns out, without the parentheses #SQL's value is interpreted as the name of a stored procedure to execute, not as a script. (I didn't know that before, but I made a small test to verify that it is indeed so.)

Related

Stored procedure to copy table using dynamic query in SQL Server

I want to create a stored procedure for coping table using dynamic query.
I followed this step for creating stored proceduce, link:-https://stackoverflow.com/questions/8698231/sql-server-stored-procedures-to-copy-tables
but I got an error:
Could not find stored procedure 'Select * into tblDetail from salesDetail'
Here is my code:
ALTER PROCEDURE sp_copyOneTableToAnother
#newtable nvarchar(50),
#oldtable nvarchar(50)
AS
BEGIN
DECLARE #sql nvarchar(1000)
SET #sql = 'SELECT * INTO ' + #newtable +
' FROM ' + #oldtable
EXEC #sql
END
exec sp_copyOneTableToAnother #newtable='tblDetail',#oldtable='salesDetail'
The stored procedure was created from above syntax but while calling sp_copyOneTableToAnother, I get an error. Please help me solve it.
There are several problems here, first, your procedure name starts with sp_, which is reserved by Microsoft for Special / System Procedures. That should go.
Next, your parameter types are wrong; the correct data type for an object is a sysname, a synonym of nvarchar(128) NOT NULL, not varchar.
Next, the injection issue; you blindly inject the values of your parameters into your statement and hope that said values aren't malicious. Validate the value of #oldtable and properly quote both parameters.
Finally, the execution should be done by sp_executesql; not using it promotes further injection issues as you can't parametrise EXEC (#SQL) statements.
You also don't define your schemas, which you really should be. I add them as NULLable parameters here, and get the USER's default schema
This results in something like this:
CREATE OR ALTER PROCEDURE dbo.CopyOneTableToAnother #NewTable sysname,
#OldTable sysname,
#NewSchema sysname = NULL,
#OldSchema sysname = NULL AS
BEGIN
SET NOCOUNT ON;
SELECT #NewSchema = ISNULL(#NewSchema,default_schema_name),
#OldSchema = ISNULL(#OldSchema,default_schema_name)
FROM sys.database_principals
WHERE name = USER_NAME();
DECLARE #SQL nvarchar(MAX);
SELECT #SQL = N'SELECT * INTO ' + QUOTENAME(#NewSchema) + N'.' + QUOTENAME(#NewTable) + N' FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + N';'
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE s.name = #OldSchema
AND t.[name] = #OldTable;
EXEC sys.sp_executesql #SQL;
END;
db<>fiddle

Pass table and column names as parameters and retrieve value

I am trying to built generic query to pass column name I want to count on and table name I want to select value.
So far this is my code:
ALTER PROCEDURE [dbo].[GenericCountAll]
#TableName VARCHAR(100),
#ColunName VARCHAR(100)
AS
BEGIN
DECLARE #table VARCHAR(30);
DECLARE #Rowcount INT;
SET #table = N'SELECT COUNT(' + #ColunName +') FROM ' + #TableName + '';
EXEC(#table)
SET #Rowcount = ##ROWCOUNT
SELECT #Rowcount
END
Trying to execute like this:
EXEC GenericCountAll 'T_User', 'Id';
but looks like I get two results, first result always returning a value of 1, and the second result returns the real count. Can anyone take a look?
Don't create dynamic sql like that! Imagine if I ran:
EXEC GenericCountAll '*/DROP PROCEDURE dboGenericCountAll;--', '1);/*';
The resulting executed SQL would be:
SELECT COUNT(1);/*) FROM */ DROP PROCEDURE dboGenericCountAll;--
That would, quite simply, DROP your procedure. And that's just a simple example. If i knew I could keep doing malicious things, I might even be able to create a new login or user, and make the a db_owner or sysadmin (depending on the permissions of what ever is being used to run that procedure).
I don't know what the point of the ##ROWCOUNT is either, I doubt that's needed. Thus, to make this SAFE you would need to do something like this:
ALTER procedure [dbo].[GenericCountAll]
#TableName sysname, --Note the datatype change
#ColumnName sysname
AS
BEGIN
DECLARE #SQL nvarchar(MAX);
SELECT N'SELECT COUNT(' + QUOTENAME(c.[name]) + N') AS RowCount' + NCHAR(10) +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.name) + N';'
FROM sys.tables t
JOIN sys.schemas s ON t.schema_id = s.schema_id
JOIN sys.columns c ON t.object_id = c.object_id
WHERE t.[name] = #TableName
AND c.[name] = #ColumnName;
/*
If either the column or the table doesn't exist, then #SQL
will have a value of NULL. This is a good thing, as it
is a great way to further avoid injection, if a bogus
table or column name is passed
*/
IF #SQL IS NOT NULL BEGIN;
PRINT #SQL; --Your best debugging friend
EXEC sp_executesql #SQL;
END ELSE BEGIN;
RAISERROR(N'Table does not exist, or the Column does not exist for the Table provided.',11,1);
END;
END

Using RETURN in dynamic SQL to exit stored procedure

I am trying to use something like below in my stored procedure to exit when a certain condition is met, but the code after the return gets executed. Is there something wrong my script?
declare #DBName sysname
set #DBName ='Test_DB'
SET #sql='IF OBJECT_ID('''+#DBName +''',''U'') is null' + CHAR(10)+ 'RETURN;'
EXEC (#sql)
You could use INSERT/EXEC to get the object id of the dynamic object you're checking for, then return if the table has no records not null:
DECLARE #DBName sysname
SET #DBName = 'Test_DB'
SET #sql='SELECT OBJECT_ID('''+#DBName +''',''U'')'
DECLARE #Results TABLE (ObjectId INT)
INSERT #Results
EXEC (#Sql)
IF NOT EXISTS (SELECT * FROM #Results WHERE ObjectId IS NOT NULL)
RETURN
Just a side note, OBJECT_ID won't match a database. You'd have to use sys.databases or like that.
I tested a similar script:
declare #DBName sysname
set #DBName ='Test_DB'
declare #sql varchar(8000)
SET #sql='IF OBJECT_ID('''+#DBName +''',''U'') is null' +
CHAR(10) +
'begin select ''before return'' return end select ''after return'''
EXEC (#sql)
The output of this script on SQL 2014 Developer Edition was 'before return' but not 'after return'. So the return does seem to be working as expected.

Using variables in Transact-sql exists subquery

this seems like it should be extraordinarily simple, so I apologize in advance if this information is easily accessible on the transact-sql documentation pages. I searched myself, but couldn't seem to find anything.
I'm trying to modify a transact-sql statement that currently runs on our Windows server 2000 box. I want to check if a table in another database exists, and then do a bunch of stuff. The database name is given as a string argument, '#dbName'
CREATE PROCEDURE CopyTables
#dbName char(4)
AS
IF EXISTS (SELECT * FROM #dbName.INFORMATION_SCHEMA.TABLES WHERE
TABLE_NAME = N'MainTable')
BEGIN
--Do Stuff
In it's current state, it doesn't like using the bare #dbName variable within the select statement. Is there special syntax for doing this?
Thanks in advance.
The below code should do what you want. As was mentioned previously, the account running the query would need the privilege to query the INFORMATION_SCHEMAs in the target database.
To future-proof your stored procedure, I'd also suggest increasing the length of the database name parameter and declaring it as an nchar or nvarchar in stead of char.
CREATE PROCEDURE CopyTables
#dbName char(4)
AS
DECLARE
#SQLStr nvarchar (max),
#Params nvarchar (max),
#Count tinyint;
SET
#Count = 0;
SET #SQLStr = N'SELECT #qCount = 1 FROM [' + #dbName + N'].INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N''MainTable''';
SET #Params = N'#qdbName char (4), #qCount tinyint OUTPUT';
EXECUTE sp_executesql #SQLStr, #Params, #qdbName = #dbName, #qCount = #Count OUTPUT;
IF #Count = 1
BEGIN
--Do Stuff
END; -- if
GO
Try doing the following:
DECLARE #dbName NVARCHAR(MAX) = 'master', #TableName NVARCHAR(MAX) = N'spt_monitor';
DECLARE #sql NVARCHAR(MAX) = N'SELECT * FROM [' + #dbName + N'].INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ''' + REPLACE(#TableName,N'''',N'''''') + N'''';
SET NOCOUNT OFF;
EXEC(#sql);
IF ##ROWCOUNT > 0 BEGIN;
-- DO STUFF
SELECT NULL;
END;
There are a few shortcomings to this solution:
1) It requires that the user executing the statement has SELECT access to the other database's INFORMATION_SCHEMA.TABLES
2) It has the side-effect of actually selecting the rows, so if you're using a reader to access the results, you'll have to call reader.NextResult() or await reader.NextResultAsync() because it actually outputs the results of the SELECT statement, rather than doing it in an IF EXISTS context.
By merging the two solutions, we get this:
DECLARE #dbName NVARCHAR(MAX) = 'master', #TableName NVARCHAR(MAX) = N'spt_monitor';
DECLARE #sql NVARCHAR(MAX) = N'SELECT #count = COUNT(*) FROM [' + #dbName + N'].INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ''' + REPLACE(#TableName,N'''',N'''''') + N'''';
DECLARE #Count INT;
EXECUTE sp_executesql #sql, N'#Count INT OUTPUT', #Count OUTPUT;
IF #Count > 0 BEGIN;
-- Do stuff
SELECT 'the table exists';
END ELSE BEGIN;
-- Do stuff
SELECT 'the table does not exist';
END;
This solution requires that the user executing the statement has SELECT access to the other database's INFORMATION_SCHEMA.TABLES, but it does not have the side-effect of selecting rows, like my previous solution.

Incorrect syntax SQL

Why do I get this error when I try to execute the following code?
I have a table NewTable1 with two columns: column1 and column2.
I get this error: Incorrect syntax near 'column2'.
--DROP COLUMN PROCEDURE
CREATE PROCEDURE DropColumn
#tableName varchar(50),
#columnName varchar(50)
AS
BEGIN
DECLARE #SQL nvarchar(500);
SET #SQL = N'ALTER TABLE ' + QUOTENAME(#tableName)
+ ' DROP COLUMN ' + QUOTENAME(#columnName);
EXEC sp_executesql #SQL;
END
RETURN 0
GO
USE SKI_SHOP;
EXEC DropColumn 'NewTable1', 'column2';
GO
Use appropriate data types. Also You will only be able to drop Columns for tables in callers default schema. Since procedure doesn't take schema into consideration, therefore you can only pass the table name and if a table exists in other than caller default schema they wont be able to delete it using this procedure .
CREATE PROCEDURE DropColumn
#tableName SYSNAME,
#columnName SYSNAME
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N' ALTER TABLE ' + QUOTENAME(#tableName)
+ N' DROP COLUMN ' + QUOTENAME(#columnName);
EXEC sp_executesql #SQL;
END
GO
I over looked some basic simple issues in my first approach, whenever creating of Dropping objects in SQL Server always check if they exist, to avoid any errors . A more complete and safe approach would be something like ...
This time I have also added schema as a parameter.
ALTER PROCEDURE DropColumn
#tableName SYSNAME,
#columnName SYSNAME,
#Schema SYSNAME,
#Success BIT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N' IF EXISTS (SELECT * FROM sys.tables t
INNER JOIN sys.columns c
ON t.[object_id] = c.[object_id]
INNER JOIN sys.schemas sc
ON t.[schema_id] = sc.[schema_id]
WHERE t.name = #tableName
AND c.name = #columnName
AND sc.name = #Schema)
BEGIN
ALTER TABLE ' + QUOTENAME(#Schema)+ '.' + QUOTENAME(#tableName)
+ N' DROP COLUMN ' + QUOTENAME(#columnName)
+ N' SET #Success = 1; '
+ N' END
ELSE
BEGIN
SET #Success = 0;
END '
EXEC sp_executesql #SQL
,N'#tableName SYSNAME, #columnName SYSNAME, #Schema SYSNAME, #Success BIT OUTPUT'
,#tableName
,#columnName
,#Schema
,#Success OUTPUT
END
GO