Stored procedure: how to use column as an input - sql

I'm trying to create a simple stored procedure to count the number of empty records in my database:
CREATE PROCEDURE dbo.cnt_empty
#col NVARCHAR(10)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
SELECT COUNT(#col) AS cnt
FROM dbo.mytable
WHERE #col = ''
END
GO
EXECUTE dbo.cnt_empty #col = N'field1' -- nvarchar(10)
I returns 0 for all the columsn I tested. What is wrong with this procedure?

Your string is not being assessed as the column name, so you are actually running "where 'field1' = ''"
You need to do something like this
set #sql = 'select #cnt = COUNT(*) from [' + #tableSchema + '].[' + #tableName +
'] where [' + #columnName + '] is not null';
-- print #sql; --uncomment for debugging
exec sp_executesql #sql, N'#cnt bigint output', #cnt = #cnt output;
Look at http://blog.hoegaerden.be/2009/02/15/script-find-all-empty-columns-in-database/ for the full script.

By doing this, your SQL statement is treating the parameter like a string, not like the name of a column. Take a look at sp_executesql. That will help you build up a SQL string and execute it.

you are matching #col (i.e. 'field1') against empty (i.e. '') in your where clause - that will never return a row.

What you want to do is declare a variable like #sql VARCHAR(500)
Then do
SET #sql = 'SELECT COUNT('+#col+') AS cnt FROM dbo.mytable'
Then try use the built in sp called sp_Executesql
http://msdn.microsoft.com/en-us/library/ms188001.aspx
This is because you are selecting the count of the variable not the count of the column.

Take a look at this article: http://www.mssqltips.com/sqlservertip/1160/execute-dynamic-sql-commands-in-sql-server/
Basically using EXEC statement or sp_executesql should be your choice.

Related

Dynamic SQL not working for changing variable column name in SQL Server

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.

SQL Server Dynamic SQL with LIKE clause and SQL Injection

I'm having a dynamically built SQL as below. My question is, is it vulnerable to SQL Injection? If yes, how should I fix it?
--search title only
if #SearchType =2
BEGIN
SET #strSQL = #strSQL + 'AND (IDownload.FileTitle LIKE ''%'+#Search+'%'' ) '
END
Yes, check:
CREATE TABLE Test (Id int)
GO
CREATE TABLE IDownload (FileTitle nvarchar(100))
DECLARE #strSQL nvarchar(max)
DECLARE #Search nvarchar(max) = 'a'') DROP TABLE Test --'
SET #strSQL = 'SELECT 1 FROM IDownload WHERE 1 = 1 '
SET #strSQL = #strSQL + 'AND (IDownload.FileTitle LIKE ''%'+#Search+'%'' ) '
PRINT #strSQL
EXEC sp_executesql #strSQL
DROP TABLE IDownload
I can drop table Test passing special string in #Search. Rewrite your code using sp_executesql procedure and parameters.

Returning passed parameter as column in result set

I have the following stored procedure code working and want to add the passed parameter #tabname as a column in the result set.
CREATE PROCEDURE CountStar
#Tabname char(10)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL varchar(250)
SELECT #SQL = 'SELECT ETL_LAST_UPD_DTTM, COUNT(*) FROM ls.' + QuoteName(#Tabname) +
'GROUP BY ETL_LAST_UPD_DTTM'
EXEC (#SQL)
SELECT #SQL = 'SELECT ETL_LAST_UPD_DTTM, COUNT(*) FROM ci.' + QuoteName(#Tabname) +
'GROUP BY ETL_LAST_UPD_DTTM'
EXEC (#SQL)
--COMMIT
END
GO
Currently this will return the last updated timestamp and the record count for the table being passed into the stored procedure for the 2 schemas identified. I want to add the #tabname to the result set as the first column followed by last updated timestamp and the record counts. This returns 2 result sets and each should look something like this for each returned result set.
Table_name Timestamp rec_cnt
--------------------------------------------------
CUSTOMERS 2015-09-24 13:10:01.1770000 378
I have tried a few things but can't get the syntax correct.
Thanks for any pointers.
Pat
CREATE PROCEDURE CountStar
#Tabname SYSNAME --<-- use appropriate data type for sql server objects
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL nvarchar(max);
SELECT #SQL = N'SELECT #Tabname AS Table_name,ETL_LAST_UPD_DTTM, COUNT(*)
FROM ls.' + QuoteName(#Tabname) +
N' GROUP BY ETL_LAST_UPD_DTTM'
EXEC sp_executesql #SQL
,N'#Tabname SYSNAME'
,#Tabname
SELECT #SQL = N'SELECT #Tabname AS Table_name,ETL_LAST_UPD_DTTM, COUNT(*)
FROM ci.' + QuoteName(#Tabname) +
N' GROUP BY ETL_LAST_UPD_DTTM'
EXEC sp_executesql #SQL
,N'#Tabname SYSNAME'
,#Tabname
END
GO

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.

Can I pass column name as input parameter in SQL stored Procedure

create procedure sp_First
#columnname varchar
AS
begin
select #columnname from Table_1
end
exec sp_First 'sname'
My requirement is to pass column names as input parameters.
I tried like that but it gave wrong output.
So Help me
You can do this in a couple of ways.
One, is to build up the query yourself and execute it.
SET #sql = 'SELECT ' + #columnName + ' FROM yourTable'
sp_executesql #sql
If you opt for that method, be very certain to santise your input. Even if you know your application will only give 'real' column names, what if some-one finds a crack in your security and is able to execute the SP directly? Then they can execute just about anything they like. With dynamic SQL, always, always, validate the parameters.
Alternatively, you can write a CASE statement...
SELECT
CASE #columnName
WHEN 'Col1' THEN Col1
WHEN 'Col2' THEN Col2
ELSE NULL
END as selectedColumn
FROM
yourTable
This is a bit more long winded, but a whole lot more secure.
No. That would just select the parameter value. You would need to use dynamic sql.
In your procedure you would have the following:
DECLARE #sql nvarchar(max) = 'SELECT ' + #columnname + ' FROM Table_1';
exec sp_executesql #sql, N''
Try using dynamic SQL:
create procedure sp_First #columnname varchar
AS
begin
declare #sql nvarchar(4000);
set #sql='select ['+#columnname+'] from Table_1';
exec sp_executesql #sql
end
go
exec sp_First 'sname'
go
This is not possible. Either use dynamic SQL (dangerous) or a gigantic case expression (slow).
Create PROCEDURE USP_S_NameAvilability
(#Value VARCHAR(50)=null,
#TableName VARCHAR(50)=null,
#ColumnName VARCHAR(50)=null)
AS
BEGIN
DECLARE #cmd AS NVARCHAR(max)
SET #Value = ''''+#Value+ ''''
SET #cmd = N'SELECT * FROM ' + #TableName + ' WHERE ' + #ColumnName + ' = ' + #Value
EXEC(#cmd)
END
As i have tried one the answer, it is getting executed successfully but while running its not giving correct output, the above works well
You can pass the column name but you cannot use it in a sql statemnt like
Select #Columnname From Table
One could build a dynamic sql string and execute it like EXEC (#SQL)
For more information see this answer on dynamic sql.
Dynamic SQL Pros and Cons
As mentioned by MatBailie
This is much more safe since it is not a dynamic query and ther are lesser chances of sql injection . I Added one situation where you even want the where clause to be dynamic . XX YY are Columns names
CREATE PROCEDURE [dbo].[DASH_getTP_under_TP]
(
#fromColumnName varchar(10) ,
#toColumnName varchar(10) ,
#ID varchar(10)
)
as
begin
-- this is the column required for where clause
declare #colname varchar(50)
set #colname=case #fromUserType
when 'XX' then 'XX'
when 'YY' then 'YY'
end
select SelectedColumnId from (
select
case #toColumnName
when 'XX' then tablename.XX
when 'YY' then tablename.YY
end as SelectedColumnId,
From tablename
where
(case #fromUserType
when 'XX' then XX
when 'YY' then YY
end)= ISNULL(#ID , #colname)
) as tbl1 group by SelectedColumnId
end
First Run;
CREATE PROCEDURE sp_First #columnname NVARCHAR(128)--128 = SQL Server Maximum Column Name Length
AS
BEGIN
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT ' + #columnname + ' FROM Table_1'
EXEC(#query)
END
Second Run;
EXEC sp_First 'COLUMN_Name'
Please Try with this.
I hope it will work for you.
Create Procedure Test
(
#Table VARCHAR(500),
#Column VARCHAR(100),
#Value VARCHAR(300)
)
AS
BEGIN
DECLARE #sql nvarchar(1000)
SET #sql = 'SELECT * FROM ' + #Table + ' WHERE ' + #Column + ' = ' + #Value
--SELECT #sql
exec (#sql)
END
-----execution----
/** Exec Test Products,IsDeposit,1 **/