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
Related
I want to write a stored procedure which can be used to update IDs of owner name, backup contact, other contacts etc in a table. These IDs are to fetched from some other table. Instead of writing different stored procedure for all these contact information, I want to write a single dynamic SQL in which I can pass the column name as a variable name.
My stored procedure looks like:
CREATE PROCEDURE spUpdateUser
(#recordid [NVARCHAR](50),
#id [NVARCHAR](10),
#user [NVARCHAR](50))
AS
BEGIN
DECLARE #sql NVARCHAR(MAX);
SET NOCOUNT ON;
SET #sql = N'UPDATE [dbo].[table1]
SET'+ QUOTENAME(#user) + ' = (SELECT [dbo].[table2].User
FROM [dbo].[table2]
WHERE [dbo].[table2].id = ' + QUOTENAME(#id) + ')
WHERE record = ' + QUOTENAME(#recordid)
EXEC sp_executesql #sql;
END;
GO
After executing the query it runs without error but the user is not changed in table1.
What is missing in the procedure?
Don't inject your parameters, parametrise them:
CREATE PROCEDURE spUpdateUser
-- Add the parameters for the stored procedure here
( #recordid [nvarchar](50), --Are your IDs really an nvarchar?
#id [nvarchar](10), --Are your IDs really an nvarchar?
#user sysname --As this is an object, lets use the correct datatype
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #sql NVARCHAR(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET NOCOUNT ON;
-- Insert statements for procedure here
SET #sql= N'UPDATE [dbo].[table1]' + #CRLF +
N'SET ' + QUOTENAME(#user) + N' = (SELECT [table2].User' + #CRLF + --3 part naming for columns is deprecated, don't use it
N' FROM [dbo].[table2]' + #CRLF +
N' WHERE [table2].id= #id)' + #CRLF + --3 part naming for columns is deprecated, don't use it
N'WHERE record = #recordid;';
--PRINT #SQL; --Your Best Friend
EXEC sp_executesql #sql, N'#id nvarchar(10), #recordid nvarchar(50)', #id, #recordid; --Assumes that IDs are an nvarchar again
END;
GO
Note I've left some comments in there for you to consume and review as well.
I have several tables having the same structure. The tables are named by year that is 2001,2002 and so on. I am in need to search a column for a value in each table and get the count for each table.
I have created a stored procedure below but I keep getting an error
Invalid column 'lol'
This is the stored procedure used:
CREATE PROCEDURE [dbo].[CountSP]
#TableName NVARCHAR(128),
#SearchParam NVARCHAR(50),
#SearchInput NVARCHAR(200)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N'SELECT COUNT('+QUOTENAME(#SearchParam)+') FROM ' + QUOTENAME(#TableName) +'WHERE'+QUOTENAME(#SearchParam)+'LIKE '+QUOTENAME(#SearchInput)+
+ N' SELECT * FROM '+QUOTENAME(#TableName)
EXECUTE sp_executesql #Sql
END
Executing it:
DECLARE #return_value INT
EXEC #return_value = [dbo].[CountSP]
#TableName = N'1999',
#SearchParam = N'USERDESC',
#SearchInput = N'lol'
SELECT 'Return Value' = #return_value
I don't know why you are using LIKE operator there while you don't use wildcards, also use SysName datatype directly for object names.
Create PROCEDURE [dbo].[CountSP]
(
#TableName SysName,
#SearchInput NVARCHAR(50),
#SearchParam SysName
)
AS
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX) = N'SELECT COUNT(' +
QUOTENAME(#SearchParam) +
N') FROM ' +
QUOTENAME(#TableName) +
N' WHERE ' +
QUOTENAME(#SearchParam) +
N' = ' + --You can change it to LIKE if needed
QUOTENAME(#SearchInput, '''') +
N';';
-- There is no benifits of using LIKE operator there
EXEC sp_executesql #SQL;
Then you can call it as
EXEC [dbo].[CountSP] N'YourTableNameHere', N'SearchInput', N'ColumnName';
This is because it is currently translated to :
SELECT COUNT([USERDESC]) FROM [1999] WHERE [USERDESC] LIKE [lol]
this means that it is comparing the "USERDESC" column with the "lol" column but from what I am understanding lol isn't a column but a value? which means you should lose the QUOTENAME for that variable.
See the documentation here : https://learn.microsoft.com/en-us/sql/t-sql/functions/quotename-transact-sql?view=sql-server-2017
You need to pass your parameter #SearchInput as a parameter to sp_execute:
CREATE PROCEDURE [dbo].[CountSP] #TableName sysname, --This is effectively the same datatype (as sysname is a synonym for nvarchar(128))
#SearchParam sysname, --Have changed this one though
#SearchInput nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sql nvarchar(MAX);
SET #Sql = N'SELECT COUNT(' + QUOTENAME(#SearchParam) + N') FROM ' + QUOTENAME(#TableName) + N'WHERE' + QUOTENAME(#SearchParam) + N' LIKE #SearchInput;' + NCHAR(13) + NCHAR(10) +
N'SELECT * FROM ' + QUOTENAME(#TableName);
EXECUTE sp_executesql #SQL, N'#SearchInput nvarchar(200)', #SearchInput;
END;
QUOTENAME, by default, will quote a value in brackets ([]). It does accept a second parameter which can be used to define a different character (for example QUOTENAME(#Value,'()') will wrap the value in parentheses). For what you want though, you want to parametrise the value, not inject (a quoted) 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
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.)
I have a stored procedure that takes in the name of a table as a parameter and uses dynamic sql to perform the select. I tried to pass #TableName as a parameter and use sp_executesql but that threw an error. I decided to go with straight dynamic sql without using sp_executesql.
Is there anything else I should be doing to secure the #TableName parameter to avoid sql injection attacks?
Stored procedure below:
CREATE PROCEDURE dbo.SP_GetRecords
(
#TableName VARCHAR(128) = NULL
)
AS
BEGIN
/* Secure the #TableName Parameter */
SET #TableName = REPLACE(#TableName, ' ','')
SET #TableName = REPLACE(#TableName, ';','')
SET #TableName = REPLACE(#TableName, '''','')
DECLARE #query NVARCHAR(MAX)
/* Validation */
IF #TableName IS NULL
BEGIN
RETURN -1
END
SET #query = 'SELECT * FROM ' + #TableName
EXEC(#query)
END
This failed when using sp_executesql instead:
SET #query = 'SELECT * FROM #TableName'
EXEC sp_executesql #query, N'#TableName VARCHAR(128)', #TableName
ERROR: Must declare the table variable
"#TableName".
See here:
How should I pass a table name into a stored proc?
you of course can look at the sysobjects table and ensure that it exists
Select id from sysobjects where xType = 'U' and [name] = #TableName
Further (more complete example):
DECLARE #TableName nVarChar(255)
DECLARE #Query nVarChar(512)
SET #TableName = 'YourTable'
SET #Query = 'Select * from ' + #TableName
-- Check if #TableName is valid
IF NOT (Select id from sysobjects where xType = 'U' and [name] = #TableName) IS NULL
exec(#Query)