Parameterised stored proc - tsql

I'm writing a parameterized stored proc. I know that you can set the parameter value such that it displays all the results when the parameter is not mentioned in the execute command.. But i'm unable to recall how that is achieved. Any help is highly appreciated... Please..

I'd recommend parameterized dynamic sql (sp_executesql)
Going this route, you can discard any irrelevant parameter when building your where clause.
Example procedure:
create proc dbo.SearchForStuff
(
#Id int = 0
,#Description varchar(100) = ''
)
as
begin
set nocount on;
declare #select nvarchar(max) = '
select
s.*
from Stuff as s'
declare #where varchar(max) = ''
if isnull(#ID,0) != 0 begin
set #where += case #where when '' then ' where ' else ' and ' end + 's.Id = #Id'
end
if isnull(#Description,'') != '' begin
set #where += case #where when '' then ' where ' else ' and ' end + 's.[Description] = #Description'
end
set #select += #where
exec sp_executesql
#select
,N'
,#Id int = 0
,#Description varchar(100) = '''''
,#Id
,#Description
end
Usage:
exec SearchForStuff #Id = 1, #Description = 'omg' -- Returns every item where Id is 1 and Description is 'omg'
exec SearchForStuff #Id = 1 -- Returns every item where Id is 1
exec SearchForStuff #Description = 'omg' -- Returns every item where Description is 'omg'
exec SearchForStuff --returns every item
In this fashion your final query is not littered with useless conditions. Further, you can get a bit more granular than I did here. Based upon which parameters were passed, you can tailor your where/join clauses to take advantage of your indexes such that you get the best possible performance. The only drawback is a slight loss of readability (imo).

You can make your WHERE conditions like this:
WHERE (#myParam IS NULL OR #myParam = someValue)
You may be able to use OPTION (RECOMPILE) is SQL2008SP1+ (or similar, don't know other options) in the sproc, depending on your RDBMS, to get this to be performant.
Method from Erland Sommarskog:
http://www.sommarskog.se/dyn-search-2008.html#static
From the link:
"The effect of all the #x IS NULL clauses is that if that input parameter is NULL, then that AND-condition is always true. Thus, the only conditions that are in effect are those where the search parameter has a non-NULL value.
As far as maintainability goes, it's difficult to think of a better solution for the search conditions at hand. It's compact, easy to read and to extend. And performance? Very good as long as you include the query hint OPTION (RECOMPILE). This hint forces the query to be recompiled each time, in which case SQL Server will use the actual variable values as if they were constants."

If it is an int you can use
SELECT X,Y
FROM T
WHERE C BETWEEN COALESCE(#P, -2147483648) AND COALESCE(#P, 2147483647)
The definitive article on the subject

Related

MSTSQL: Can a SP return both an out param and a result set

I wish to know whether it's feasible to have a TSQL stored procedure return both a result set and the output parameter like so.
create procedure uspReadMyXmlInformation(#myXmlDoc xml, #myProductNum varchar(18) output) as
begin
set nocount on;
declare #myXmlContent table(MyOrderId varchar(12) not null
,CreatedAt datetime not null)
insert into #myXmlContent
select x.c.value('MyOrderID[1]', 'varchar(12)')
x.c.value('CreatedAt[1]', 'datetime')
from #myXmlDoc.nodes('MyRootNodeName/MyChildNodeName') x(c)
set #myProductNum='MyProductNum'
select *
from #myXmlContent
return;
end
So, what happens here is that I can either obtain the result set, when I remove the output parameter, or I obtain the output parameter and the result set is always empty (0=count(*)).
Is there anyway I can obtain both with the same stored procedure or I'd better split them?
I think it's doable from this post in Oracle. I'd like to achieve the same in SQL Server, although constrained to the 2008 version.
Oracle stored procedure: return both result set and out parameters
What I like from doing it using the same SP is that both the result set and the output parameter represent information I read from the XML document. So, the name of the SP says it all, somehow.
EDIT
As some think it might be a duplicate of:
Possible to return an out parameter with a DataReader
I don't think it is as answers there are related as to how the DataReader behaves more than how it could be achieved with TSQL.
The fact is that I get the the value from the output parameter, but I don't get it from the result set at all, it's always returning null.
So, I'm on a SQL Server only project and I'd need that. Otherwise, I'll split it in two, if I can't achieve it in a timely fashion.
Here's how it's used:
declare #xmlInformationData table(MyOrderId varchar(12) not null
,CreatedAt datetime not null)
insert into #xmlInformationData
execute uspReadMyXmlInformation #myXmlDoc, #myProductNum output
while 0<(select count(*) from #xmlInformationData)
begin
-- This will never be executed because I have no rows in #xmlInformationData
-- And yet, before the loop, I have my out param value!
end
The following is a trivial demonstration of using both an output parameter and result set. Try running it a few times and the results should vary.
create procedure Arthur( #TheAnswer as Int Output ) as
begin
-- Set the value of the output parameter.
set #TheAnswer = 42;
-- Generate a single row most of the time.
select GetDate() as NextVogonPoetryReading
where DatePart( millisecond, GetDate() ) < 750;
end;
go 1
-- Declare the variables for the test.
declare #HHGTTU as Table ( HHGTTUId Int Identity, NextVogonPoetryReading DateTime );
declare #SixTimesNine as Int;
-- Execute the SP once so that the while loop might.
insert into #HHGTTU ( NextVogonPoetryReading )
execute Arthur #TheAnswer = #SixTimesNine Output;
-- See what happens.
while exists ( select Pi() from #HHGTTU )
begin
-- See where we are at.
select #SixTimesNine as SixTimesNine, Max( HHGTTUId ) as MaxHHGTTUId, Max( NextVogonPoetryReading ) as MaxNextVogonPoetryReading
from #HHGTTU;
-- Reset.
delete from #HHGTTU;
set #SixTimesNine = 54;
select #SixTimesNine as SixTimesNineAfterReset;
waitfor delay '00:00:00.100';
-- Execute the SP again.
insert into #HHGTTU ( NextVogonPoetryReading )
execute Arthur #TheAnswer = #SixTimesNine Output;
end;
Aside: My apologies for the trauma introduced into your life by my mention of a DataReader. I was merely attempting to pass on my experience in a C# application without getting into the weeds of exactly what sort of connection to the database you are using, which driver(s) might be involved, ... .

SQL Server stored procedure: delete record dynamically

I need to delete (read/update) some values in some tables, and would like to use a stored procedure to reduce security issues.
Since tables are many and records even more and it is not reasonable to write a stored procedure for each combination, and since everybody has this kind of need, I thought could have been easy to find a stored procedure to do this.. but googling a lot I did not find a simple and short answer, so I tried to build my own stored procedure. But I'm afraid it could have some security issues: principally when I declare #Table as nvarchar(30).. I tried to declare as TABLE but it returns error..
Can suggest what is not acceptable and suggest a solution?
Thanks
Here the stored procedure for deleting.. but for other action could be similar:
CREATE PROCEDURE dbo.spDeleteRecord
(
#UID nvarchar(20) = NOT NULL,
#PWD nvarchar(30) = NOT NULL,
#Table sysname,
#WhereField sysname,
#WhereValue nvarchar(150) = NOT NULL
)
AS
SET NOCOUNT ON;
DECLARE #S nvarchar(max) = '',
#P nvarchar(max) = ''
SET #S = 'DELETE t
from dbo.'+quotename(#Table)+' t
join dbo.subUsers su on t.UID = su.UID
where ' + quotename(#WhereField) + ' = #_WhereValue
and su.SUID = #_UID
and su.PWD = #_PWD'
SET #P = '#_UID nvarchar(50),
#_PWD nvarchar(50),
#_Table sysname,
#_WhereField sysname,
#_WhereValue nvarchar(150)'
--PRINT #S
EXEC sp_executesql #S, #P, #UID, #PWD, #Table, #WhereField, #WhereValue
SET NOCOUNT OFF
Thanks for reading
Joe
You would need to do it like this:
SET #S = 'DELETE t
from dbo.'+quotename(#Table)+' t
join dbo.subUsers su on t.UID = su.UID
where ' + quotename(#WhereField) + ' = #_WhereValue
and su.SUID = #_UID
and su.PWD = #_PWD'
SET #P = '#_UID nvarchar(50),
#_PWD nvarchar(50),
#_WhereValue nvarchar(150)'
--PRINT #S
EXEC sp_executesql #S, #P, #_WhereValue = #WhereValue, #_UID = #UID, #_PWD = #PWD
Basically, the parameter list can only refer to parameters that are actually embedded in the SQL string.
Also, note that #Table and #WhereField would be more correct as datatype sysname. I would also probably have restricted #UID, #PWD, and #WhereValue to NOT NULL because I hate unhandled nulls.
However, you really need to consider if you want to do this. To me this feels like leaving a loaded gun lying around. What happens when you call this with a table that happens to have a UID field that happens to coincide to the values in the dbo.subUsers table even though no relation exists there? I don't see any significant advantage of this method over just running the parameterized query from your application, and the fact that the query changes between executions means that you may run into problems with parameter sniffing so you may end up with suboptimal execution plans.

TSQL: Determine number of columns returned by Stored Procedure

This probably has been asked before, but I was unable to find a satisfying answer.
I need to insert results of a stored procedure into a temporary table, something like:
INSERT INTO #TEMP EXEC MY_SP
I don't know in advance how many columns the SP will return, so I need to prepare my #TEMP table (via dynamic ALTER .. ADD commands) to add columns to match SP resultset.
Assumption is - SP accepts no parameters and number of columns is always the same. But how do I determine that number in pure TSQL outside of SP so I can store it for example into a variable?
Tough one, especially if someone else is denying you the necessary permissions to run e.g. OPENROWSET.
Have you considered unpacking/script the SP and add its contents directly to your T-SQL? In this way you can modify and adapt it however you may want.
Otherwise, if you could explain more about the SP:
What does the SP do?
What kind of information does it output? One-N columns, - how many rows?
Is it slow/fast? (Could we perhaps use a brute-force [try-catch] approach)?
How does it determine the columns to output and how often does that change?
Can you pre-determine the columns in any way? (So that you may use an INSERT #temp EXEC sp_getData syntax).
Best of luck!
It's a bit awkward, but you can do something like:
SELECT * INTO #temp
FROM OPENROWSET('SQLOLEDB','Data Source=MyServer;Trusted_Connection=yes;Integrated Security=SSPI', 'EXECUTE MyDB.MySchema.MyProcedure #MyParm=123')
I've requested an EXECUTE INTO syntax, like SELECT INTO to avoid having to know the shape of the stored proc output in advance, but it was rejected
Let me say from the start that if I had to do this I would try find a way to do it outside the SQL environment or something else, because the solution I am providing is not a good way to do this, but it works. So I am not saying that this is a good idea.
I have a sp called test:
CREATE PROCEDURE Test
AS
SELECT 1 as One, 2 as Two
To execute this dynamically I do the following:
DECLARE #i int
SET #i = 1;
DECLARE #SUCESS bit
SET #SUCESS = 0
WHILE(#SUCESS = 0)
BEGIN
DECLARE #proc VARCHAR(MAX)
DECLARE #count int
SET #count = 1
SET #proc = 'DECLARE #t TABLE ( c1 varchar(max) '
WHILE #count < #i
BEGIN
SET #proc = #proc + ', c' + CONVERT(varchar, #count + 1) + ' varchar(max) '
print #proc
SET #count = #count + 1
END
SET #proc = #proc + '); INSERT INTO #t EXEC Test'
BEGIN TRY
EXEC(#proc);
SET #SUCESS = 1
END TRY
BEGIN CATCH
SET #i = #i+1
END CATCH
END
SET #proc = #proc + '; SELECT * into ##t FROM #t '
EXEC( #proc )
SELECT * from ##t
This is a poor solution to your problem because you have lost the data type of your columns, their names etc.
I don't understand the syntax, and this probably isn't the best way, but someone seems to have done this with converting to xml and parsing it: Dynamic query results into a temp table or table variable

ASP - SQL injection protection in the SELECT clause

After getting great help in securing against SQL injection from classic ASP protection against SQL injection, I've encountered a major issue which cannot be solved using parameterized queries.
name = Trim(Request.QueryString("name"))
flds = Trim(Request.QueryString("flds"))
sql = "set rowcount 0 select " & flds & " from [TABLE] where Name = '" & name & "'"
From what I understand, a parameterized query will protect against SQL injection in the WHERE clause (in this case, the name field.
flds is a comma-separated list of parameters that the users wants returned. As it is obvious, it is very vulnerable to SQL injection.
One idea I have to secure my code is to have a statically generated dict of valid fields, split the flds string by ",", verify each one of the values against the dict, and construct the SQL query that will consist of all the fields that are present in the dict.
It seems to me that although this method will work for security, it will require me to modify the static list at every change in the database (however rare those are).
Are there better/proper ways of securing this code against SQL injection attacks?
Create a split function in SQL Server (there are better ones for newer versions, but this is what you get in SQL Server 2000):
CREATE FUNCTION dbo.SplitStrings
(
#List NVARCHAR(4000),
#Delimiter CHAR(1)
)
RETURNS #Items TABLE
(
Item NVARCHAR(4000)
)
AS
BEGIN
DECLARE
#Item VARCHAR(12),
#Pos INT;
WHILE LEN(#List)>0
BEGIN
SET #Pos = CHARINDEX(#Delimiter, #List);
IF #Pos = 0
SET #Pos = LEN(#List)+1;
SET #Item = LEFT(#List, #Pos-1);
INSERT #Items SELECT LTRIM(RTRIM(#Item));
SET #List = SUBSTRING(#List, #Pos + LEN(#Delimiter), LEN(#List));
IF LEN(#List) = 0 BREAK;
END
RETURN;
END
GO
Then create a stored procedure:
CREATE PROCEDURE dbo.RunScaryQuery
#columns NVARCHAR(4000),
#table NVARCHAR(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #collist NVARCHAR(4000), #sql NVARCHAR(4000);
SELECT #collist = COALESCE(#collist + ',', '') + c.name
FROM syscolumns AS c
INNER JOIN dbo.SplitStrings(#columns, ',') AS s
ON s.Item = c.name
WHERE c.id = OBJECT_ID(#table);
SELECT #sql = 'SELECT ' + #collist + ' FROM ' + #table
-- where ...
;
EXEC sp_executesql #sql;
END
GO
Now call that stored procedure from ASP with a properly parameterized command object.
This will ensure that your SQL query is generated only using column names that actually exist in the table. (Any nonsense will be ignored.)
This presumes that you will get at least one valid column name in the list.
I'm at home, no db to test but this should do it
Basically, get all the fields from the db that fit the where, get the requested fields in an array and compare the two lists, putting out only the requested fields.
name = Trim(Request.QueryString("name"))
flds = split(Trim(Request.QueryString("flds")),",")
sql = "set rowcount 0 select * from [TABLE] where Name = '" & name & "'"
set oRst = oConn.execute(sql)
on error resume next
do while not oRst.eof
result = ""
separator = ""
for each field in flds
for each requested_field in flds
if uCase(field.name) = uCase(trim(requested_field)) then
result = result & separator & field.value
separator = ","
end if
next
next
response.write result & "<br>"
oRst.movenext
loop
hm... so I'm going with another solution.
I first have an SQL query that return all the valid fields
select
tcol.name
from
sysObjects tobj
join syscolumns tcol on tobj.id = tcol.id
where
tobj.xtype = 'U'
and tobj.name = '[TABLE]'
and then I validate every element as suggested by #peter. All the validated parameters are then used to build the query string, and name is passed as a parameter in the second query.
This seems to minimize the overhead and the strain on the database.
Have a look at http://www.userfriendlythinking.com/Blog/BlogDetail.asp?p1=7013&p2=119&p7=3001
which shows usage of parameterized queries.

SQL server name of columns as variables

I have the following statement in a stored procedure. I am passing the name of the column as parameter and also the value to be checked in another variable. Is it possible to accomplish this in SQL server. Please let me know.
SELECT CaseId FROM app_Case
where #SearchCat=#keywords
ORDER BY CreatedDate DESC
I think the only way to do this would be to generate a dynamic SQL statement. The other option would be to take all column values as parameters, default them to null, and check for that.
ie
WHERE (cola = #cola OR #cola IS NULL) AND (colb = #colb OR #colb IS NULL) etc.
You need to create a string of SQL inside the SP and execute it.
Declare #SQL As VARCHAR(8000)
SET #SQL = 'SELECT CaseId FROM app_Case where ' +
#SearchCat + ' = '' + #keywords +
'' ORDER BY CreatedDate DESC'
EXEC(#SQL)
You can build a dynamic query Essentially you build a string and then execute it. (Watch out for SQL injection attacks).
Another approach would be to use a case statement which if you don't have a lot of options might be worth trying:
select CaseId from app_Case
where case when #searchCat='field1'
then field1
else #searchVal
end = #searchVal and
case when #searchCat='field2'
then field2
else #searchVal
end = #searchVal
Another approach is do the same thing using or clauses:
select CaseId from app_Case
where (#searchCat='Field1' and Field1=#searchVal) OR
(#serachCat='Field2' and Field2=#searchVal)