Executing a stored procedure using the 'IN' clause - sql

I've got the following (very simplified) stored procedure:
CREATE PROCEDURE [dbo].[sp_Search]
#MatchGender varchar(6)
AS
BEGIN
SELECT
*
FROM [tblUsers]
WHERE [UserGender] in (#MatchGender)
END
GO
I want to be able to match either Male, Female or both, so I'm using the "IN' clause.
When I test my stored procedure, I run the following:
exec [sp_Search] 'F'
Works fine. Now, how would I run that for both genders? When I try:
exec [sp_Search] ('F', 'M')
It doesn't work. What's the proper syntax? Please note, some of these matches have a couple dozen options, so assume a user can select 6 or 7 out of a possible 20. Not all of my variables are as simple as M/F).

I won't pretend this is the ideal answer from an overall design perspective, but here's one approach to interpret what you're trying:
CREATE PROCEDURE [dbo].[sp_Search]
#MatchGender nvarchar(max)
AS
BEGIN
Declare #statement nvarchar(max)
Set #Statement = "select * from [tblUsers] where UserGender in (" + #MatchGender +")"
exec #statement
end
Now, some obvious caveats to this is that you must make sure the parameter string is properly formatted, eg "'M','F'" so it will form a proper list when EXEC'ed.
Again, this might not be the ideal notion from an overall design, (lots of security issues, sql injection risks, etc) but this should illustrate one way to accomplish what you're trying. Untested :)

Related

Should I always use dynamic sql when programatically use some stored procedure?

I have a stored procedure that can get the number of records in a table, in which the #tableName is the parameter of the stored procedure. Let's call it FastCount:
SELECT OBJECT_NAME(object_id), SUM(row_count) AS rows
FROM sys.dm_db_partition_stats
WHERE object_id = OBJECT_ID(#tableName)
AND index_id < 2
GROUP BY OBJECT_NAME(object_id);
Now, let's say I have 50 tables, like data_1950, data_1951, .....data_2000. I wrote a batch, query each table's records count, and put them into a temporary table. It works like a charm
CREATE TABLE #Temp
(
TableName varchar(30),
RecordsCount int
)
DECLARE #sql as varchar(max)
DECLARE #yearN as int = 1950
DECLARE #tbName as sysname
WHILE #yearN <= 2000
BEGIN
SET #tbName = QUOTEName(N'[dbo].data_' + Convert(varchar,#yearN))
SET #sql = N'Exec [dbo].FastCount #tableName=' + #tbName
INSERT INTO #Temp
EXEC (#sql)
SET #yearN = #yearN + 1
END
SELECT * FROM #Temp
DROP TABLE #Temp
However, if I replace the dynamic SQL string part
SET #sql = N'Exec [dbo].FastCount #tableName=' + #tbName
INSERT INTO #Temp
EXEC (#sql)
with a straightforward call
INSERT INTO #Temp
EXEC [dbo].FastCount #tableName = #tbName
Then the whole batch just not work...
So I don't understand why... Should I always use dynamic SQL string and exec(#sql) when programmatically using the stored procedure. A big thanks for taking the time to look.
OK, here is what is happening in the two scenarios that you posed in your original question. (Yes, the reality is that there are probably better ways to achieve your end result, but let's look at the actual problem that you posed .... why is the behaviour of your INSERT / EXEC different, depending on how you made the call).
First, you have your variable declared, that will contain your table name:
DECLARE #tbName as sysname
Then you have your looping block that incrementally increases the year number, to generate the different table names. There's nothing inherently wrong with the looping block, so let's just look at an example using one of the table names, to see what's happening within the WHILE block. Take the first table name as the example, which would be [dbo].data_1950.
Your statement:
set #tbName = QUOTEName(N'[dbo].data_' + Convert(varchar,#yearN))
ultimately takes the string "[dbo].data_1950" - which comes from concatenating '[dbo].data_' with the year number (in this case, 1950) converted to a string (varchar) - and passes it to the QUOTENAME() function. The QUOTENAME() function takes its input and a second parameter, which is the character that the input should be quoted with (if the 2nd parameter is not passed, then the default is []). Thus, if we then converted the #tbName variable to a string, it would appear like this:
[[dbo].data_1950]
Now we get to see the funky way that SQL deals with "sysname" data-types. (In fact, as you read further down, maybe the issue is not primarily tied to the "sysname" data-type, but anyhow, take away from this what you will). To be honest, "sysname" is, in itself, a little bit of a funky data-type anyway, which I tend to steer away from, unless absolutely necessary. But anyhow, on to the details of the issue that you were seeing.
Step 1 - I created a version of your stored proc, but I included a statement that would output the value of the #tableName parameter that was passed in. This gives us an opportunity to see what SQL is doing in the two different scenarios, and then explain why the results are different.
CREATE PROC [dbo].FastCount
(
#tableName varchar(100)
)
AS
BEGIN
PRINT #tableName;
SELECT OBJECT_NAME(object_id), SUM(row_count) AS rows
FROM sys.dm_db_partition_stats
WHERE object_id = OBJECT_ID(#tableName)
AND index_id < 2
GROUP BY OBJECT_NAME(object_id);
END
Step 2 - our first scenario is executing the dynamic SQL.
set #tbName = QUOTEName(N'[dbo].data_' + Convert(varchar,#yearN))
set #sql = N'Exec [dbo].FastCount #tableName=' + #tbName
Insert Into #Temp Exec(#sql)
Now, we know that the #tbName variable contains
[[dbo].data_1950]
and therefore we can then infer that the #sql variable contains
Exec [dbo].FastCount #tableName=[[dbo].data_1950]
so that is effectively the statement that is executed by the Exec(#sql) command.
When this runs, and we look at the output of the PRINT command, we see
[dbo].data_1950
and we see a result from our query (the table name and row count). This makes sense, of course, because our table name is "data_1950", and the schema of the table is "dbo", so the SELECT statement to get the row count is going to work as expected.
Step 3 - run the EXEC command directly, without the use of the #sql variable, ie.
Insert Into #Temp Exec [dbo].FastCount #tableName = #tbName
Now, when we look at the output of the PRINT command for this execution of the "FastCount" stored procedure, we see
[[dbo].data_1950]
Of course, this is now NOT going to produce the results that we expect, because we're telling SQL to find the row count for a table named "[dbo].data_1950" (in the absence of the specific schema, SQL will just assume the default schema. In this case, with a schema of [dbo], we'd be telling SQL to get the row count from a table named [dbo].[[dbo].data_1950] - which is clearly NOT the table name).
You should see the obvious difference - in one scenario, the parameter value that is passed into the stored is the "correct" reference to the table name, and in the other scenario it is not.
As a final step, let's look at how the "non-dynamic" SQL would be executed, to achieve the results that we need. In this instance, there's no need for the QUOTENAME() function:
set #tbName = N'[dbo].data_' + Convert(nvarchar,#yearN)
Insert Into #Temp Exec [dbo].FastCount #tableName = #tbName
When we run it in this way, we see the expected output ([dbo].data_1950) from the PRINT command, and we see the expected query results (containing the table name and row count).
Can I explain this behaviour, exactly? Errr, not necessarily ... maybe someone else will be able to explain specifically what is happening, and why. My only interpretation is that when the EXEC() statement is passed the dynamic sql (ie. #sql variable) it is first interpreting the entire string and stripping out identifiers (in the case, the surrounding [] ... on what basis is it making that decision, I don't know). As opposed to the non-dynamic execution, where the #tbName value ([[dbo].data_1950]) is just being passed straight in as the parameter, with no modification (and thus causing the unexpected end result that we saw).
Hopefully this information is useful to you (or, at least, to someone else in the future!).
In general you should avoid dynamic SQL, and you should avoid granting rights to execute dynamic SQL, unless absolutely necessary. This is for performance and security reasons.
The typical way to deal with this situation is to use a partitioned view:
CREATE VIEW DataView
AS
SELECT '1950' TableName, * FROM Data_1950
UNION ALL
SELECT '1951' TableName, * FROM Data_1951
UNION ALL
SELECT '1952' TableName, * FROM Data_1952
UNION ALL
SELECT '1953' TableName, * FROM Data_1953
UNION ALL
SELECT '1954' TableName, * FROM Data_1954
UNION ALL
SELECT '1955' TableName, * FROM Data_1955
(Keep adding select statements until you have covered all of your tables.)
Now to get your table counts all you need to do is execute this:
SELECT TableName, COUNT(*) RecordCount
FROM DataView
GROUP BY TableName
Isn't that much easier?

Set table based on stored procedure parameter

We have a process that updates certain tables based on a parameter passed in, specifically a certain state. I know organizationally this problem would be eliminated by using a single table for this data, but that is not an option -- this isn't my database.
To update these tables, we run a stored procedure. The only issue is that there was a stored procedure for each state, and this made code updates horrible. In order to minimize the amount of code needing to be maintained, we wanted to move towards a single stored procedure that takes in a state parameter, and updates the correct tables. We wanted this without 50 If statements, so the only way I could think to do this was to save the SQL code as text, and then execute the string. IE:
SET #SSQL = 'UPDATE TBL_' + #STATE +' SET BLAH = FOO'
EXEC #SSQL;
I was wondering if there was a way to do this without using strings to update the correct tables based on that parameter. These stored procedures are thousands of lines long.
Thanks all!
Instead save entire script as SQL text and execute it, just update the required table using like code below as where you need and rest continue as it is
EXEC('UPDATE TBL_' + #STATE +' SET BLAH = FOO')
You could, indeed, use dynamic SQL (the exec function) - but with long, complex stored procedures, that can indeed be horrible.
When faced with a similar problem many years ago, we created the stored procedures by running a sort of "mail-merge". We'd write the procedure to work against a single table, then replace the table names with variables and used a PHP script to output a stored procedure for each table by storing the table names in a CSV file.
You could replicate that in any scripting language of your choice - it took about a day to get this to work. It had the added benefit of allowing us to easily store the stored proc templates in source code control.
You can safely use sp_executesql which is fairly more appropriate than a simple EXEC command. To do so, even with input and output parameters :
DECLARE #sql nvarchar(4000),
#tablename nvarchar(4000) = 'YOUR_TABLE_NAME',
#params nvarchar(4000),
#count int
SELECT #sql =
N' UPDATE ' + #tablename +
N' SET Bar = #Foo;' +
N' SELECT #count = ##rowcount'
SELECT #params =
N'#Foo int, ' +
N'#count int OUTPUT'
EXEC sp_executesql #sql, #params, 2, #count OUTPUT
SELECT #count [Row(s) updated]
I encourage you reading the related part of the article mentionned here.

Select from table with current user's schema

I am working in a database where each user has a table in their schema called FindResults
eg: MyDatabase.User1.FindResults, MyDatabase.User2.FindResults, etc.
If I run a SELECT query on this table while logged in as one of the users it works just fine. However, I have a stored procedure (MyDatabase.dbo.ReadFindResults) that tries to run a SELECT query on this table, it fails because it tries to read MyDatabase.dbo.FindResults (does not exist). I have gotten around this by using dynamic SQL, but I was hoping there was a way to avoid this.
Is there a way to tell the stored procedure to use the current user's schema or perhaps something to change the scope to allow it to find the table I want?
EDIT: Here is the code for the stored procedure
-- Returns the IDs contained in the given find results set
CREATE PROCEDURE [dbo].[ReadFindResults]
#resultsid int -- The ID of the results set
AS
BEGIN
SELECT objectid FROM FindResults WHERE resultsid = #resultsid
END
GO
In your stored procedure you can say:
SELECT cols FROM MyDatabase..FindResults;
(Leaving out the schema name.)
However this seems very error-prone, and IMHO you should either have separate, schema-bound stored procedures as well, or a single table with a column to indicate whose row it is.
A hack could be to execute as the username:
DECLARE #sql NVARCHAR(255) = N'EXECUTE AS USER = N''' + SUSER_SNAME() + '''';
EXEC sp_executesql #sql;
SELECT whatever FROM Findresults;
But I haven't had the opportunity to test this (you're better equipped to test in your scenario anyway).
It still seems like a remarkably avoidable problem in the first place, but maybe that's just me.
You could use dynamic SQL like this:
DECLARE #sql NVARCHAR(1024) = "SELECT something FROM [userName].someTable"
DECLARE #userName NVARCHAR(255) = SUSER_SNAME()
SET #sql = REPLACE(#sql, "[userName]", #userName)
EXEC sp_executesql #sql
That too is a hakish way of doing that query. More reading about dynamic SQL here: http://www.sommarskog.se/dynamic_sql.html
Im at home right now and do not have access to a SQL Server. I will edit my post in the morning when Im at work if needed.

how to concatenate varying stored procedure parameters

please help me with writing this search sql stored procedure
procedure may have different number of parameters at different time
so could any body help me with writing this query. I don't know how to concatenate parameters.
i am new to stored procedure
CREATE PROCEDURE searchStudent
-- Add the parameters for the stored procedure here
#course int=null,
#branch int=null,
#admissionYear varchar(max)=null,
#passingYear varchar(max)=null,
#userName varchar(max)=null,
#sex varchar(max)=null,
#studyGap varchar(max)=null,
#firstName varchar(max)=null,
#lastName varchar(max)=null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE query STR DEFAULT null
IF #course IS NOT NULL
THEN query=
SELECT * FROM [tbl_students] WHERE
END
GO
please complete the query so that it can have parameters which are having values and can search from database on the basis of parameters value. But parameter may vary every time depends on search criteria.
You would probably need to use Dynamic SQL to achieve this. First of all I would highly recommend reading this excellent article. http://www.sommarskog.se/dynamic_sql.html
You're dynamic sql would be something like this;
Declare #query varchar(max)
Set #query = 'Select * From dbo.MyTable Where '
If #Course Is Not Null
Begin
Set #query = #query + 'Course = ' + Convert(varchar(10), #Course)
end
If #Branch Is Not Null
Begin
Set #query = #query + ' and Branch = ' + Convert(varchar(10), #Branch )
end
This is only an example! You will need to build in some checks to ensure that you have one (and only one) Where clause, you must ensure that the integer values are converted to string values correctly. You must also check that the parameters don't have any special characters that could break the dynamic sql - like an apostrophe (')
Using dynamic SQL can be painful and very difficult to get right.
Good luck!
The key with a dynamic search conditions is to make sure an index is used, instead of how can I easily reuse code, eliminate duplications in a query, or try to do everything with the same query. Here is a very comprehensive article on how to handle this topic:
Dynamic Search Conditions in T-SQL by Erland Sommarskog
It covers all the issues and methods of trying to write queries with multiple optional search conditions. This main thing you need to be concerned with is not the duplication of code, but the use of an index. If your query fails to use an index, it will preform poorly. There are several techniques that can be used, which may or may not allow an index to be used.
here is the table of contents:
Introduction
The Case Study: Searching Orders
The Northgale Database
Dynamic SQL
Introduction
Using sp_executesql
Using the CLR
Using EXEC()
When Caching Is Not Really What You Want
Static SQL
Introduction
x = #x OR #x IS NULL
Using IF statements
Umachandar's Bag of Tricks
Using Temp Tables
x = #x AND #x IS NOT NULL
Handling Complex Conditions
Hybrid Solutions – Using both Static and Dynamic SQL
Using Views
Using Inline Table Functions
Conclusion
Feedback and Acknowledgements
Revision History
Sorry, I am having trouble understanding what you are asking. Do you mean the consumer of the sproc may specify some arbitrary subset of the parameters and you want to filter on those?
Assuming the above you have 2 options.
1.
use a where clause something like this:
WHERE ([tbl_students].firstName = ISNULL(#firstname,firstName)
AND ([tbl_students].lastName = ISNULL(#lastName ,lastName )
etc.
What this does is check if your parameter has a value, and, if so, it will compare it to the column. If the param is null, then it will compare the column to itself, which will never filter anything out.
use dynamic sql in your sproc and just include the line of the where clause you want if the param is not null.

How to Suppress the SELECT Output of a Stored Procedure called from another Stored Procedure in SQL Server?

I'm not talking about doing a "SET NOCOUNT OFF". But I have a stored procedure which I use to insert some data into some tables. This procedure creates a xml response string, well let me give you an example:
CREATE PROCEDURE [dbo].[insertSomeData] (#myParam int) AS
DECLARE #reply varchar(2048)
... Do a bunch of inserts/updates...
SET #reply = '<xml><big /><outputs /></xml>'
SELECT #reply
GO
So I put together a script which uses this SP a bunch of times, and the xml "output" is getting to be too much (it's crashed my box once already).
Is there a way to suppress or redirect the output generated from this stored procedure? I don't think that modifying this stored procedure is an option.
thanks.
I guess i should clarify. This SP above is being called by a T-SQL Update script that i wrote, to be run through enterprise studio manager, etc.
And it's not the most elegant SQL i've ever written either (some psuedo-sql):
WHILE unprocessedRecordsLeft
BEGIN
SELECT top 1 record from updateTable where Processed = 0
EXEC insertSomeData #param = record_From_UpdateTable
END
So lets say the UpdateTable has some 50k records in it. That SP gets called 50k times, writing 50k xml strings to the output window. It didn't bring the sql server to a stop, just my client app (sql server management studio).
The answer you're looking for is found in a similar SO question by Josh Burke:
-- Assume this table matches the output of your procedure
DECLARE #tmpNewValue TABLE ([Id] int, [Name] varchar(50))
INSERT INTO #tmpNewValue
EXEC [ProcedureB]
-- SELECT [Id], [Name] FROM #tmpNewValue
I think I found a solution.
So what i can do now in my SQL script is something like this (sql-psuedo code):
create table #tmp(xmlReply varchar(2048))
while not_done
begin
select top 1 record from updateTable where processed = 0
insert into #tmp exec insertSomeData #param=record
end
drop table #tmp
Now if there was a even more efficient way to do this. Does SQL Server have something similar to /dev/null? A null table or something?
Answering the question, "How do I suppress stored procedure output?" really depends on what you are trying to accomplish. So I want to contribute what I encountered:
I needed to supress the stored procedure (USP) output because I just wanted the row count (##ROWCOUNT) from the output. What I did, and this may not work for everyone, is since my query was already going to be dynamic sql I added a parameter called #silentExecution to the USP in question. This is a bit parameter which I defaulted to zero (0).
Next if #silentExecution was set to one (1) I would insert the table contents into a temporary table, which is what would supress the output and then execute ##ROWCOUNT with no problem.
USP Example:
CREATE PROCEDURE usp_SilentExecutionProc
#silentExecution bit = 0
AS
BEGIN
SET NOCOUNT ON;
DECLARE #strSQL VARCHAR(MAX);
SET #strSQL = '';
SET #strSQL = 'SELECT TOP 10 * ';
IF #silentExecution = 1
SET #strSQL = #strSQL + 'INTO #tmpDevNull ';
SET #strSQL = #strSQL +
'FROM dbo.SomeTable ';
EXEC(#strSQL);
END
GO
Then you can execute the whole thing like so:
EXEC dbo.usp_SilentExecutionProc #silentExecution = 1;
SELECT ##ROWCOUNT;
The purpose behind doing it like this is if you need the USP to be able to return a result set in other uses or cases, but still utilize it for just the rows.
Just wanted to share my solution.
I have recently come across with a similar issue while writing a migration script and since the issue was resolved in a different way, I want to record it.
I have nearly killed my SSMS Client by running a simple while loop for 3000 times and calling a procedure.
DECLARE #counter INT
SET #counter = 10
WHILE #counter > 0
BEGIN
-- call a procedure which returns some resultset
SELECT #counter-- (simulating the effect of stored proc returning some resultset)
SET #counter = #counter - 1
END
The script result was executed using SSMS and default option on query window is set to show “Results to Grid”[Ctrl+d shortcut].
Easy Solution:
Try setting the results to file to avoid the grid to be built and painted on the SSMS client. [CTRL+SHIFT+F keyboard shortcut to set the query results to file].
This issue is related to : stackoverflow query
Man, this is seriously a case of a computer doing what you told it to do instead of what you wanted it to do.
If you don't want it to return results, then don't ask it to return results. Refactor that stored procedure into two:
CREATE PROCEDURE [dbo].[insertSomeData] (#myParam int) AS
BEGIN
DECLARE #reply varchar(2048)
--... Do a bunch of inserts/updates...
EXEC SelectOutput
END
GO
CREATE PROCEDURE SelectOutput AS
BEGIN
SET #reply = '<xml><big /><outputs /></xml>'
SELECT #reply
END
From which client are you calling the stored procedure? Say it was from C#, and you're calling it like:
var com = myConnection.CreateCommand();
com.CommandText = "exec insertSomeData 1";
var read = com.ExecuteReader();
This will not yet retrieve the result from the server; you have to call Read() for that:
read.Read();
var myBigString = read[0].ToString();
So if you don't call Read, the XML won't leave the Sql Server. You can even call the procedure with ExecuteNonQuery:
var com = myConnection.CreateCommand();
com.CommandText = "exec insertSomeData 1";
com.ExecuteNonQuery();
Here the client won't even ask for the result of the select.
You could create a SQL CLR stored procedure that execs this. Should be pretty easy.
I don't know if SQL Server has an option to suppress output (I don't think it does), but the SQL Query Analyzer has an option (under results tab) to "Discard Results".
Are you running this through isql?
You said your server is crashing. What is crashing the application that consumes the output of this SQL or SQL Server itself (assuming SQL Server).
If you are using .Net Framework application to call the stored procedure then take a look at SQLCommand.ExecuteNonQuery. This just executes stored procedure with no results returned. If problem is at SQL Server level then you are going to have to do something different (i.e. change the stored procedure).
You can include in the SP a parameter to indicate if you want it to do the select or not, but of course, you need to have access and reprogram the SP.
CREATE PROCEDURE [dbo].[insertSomeData] (#myParam int, #doSelect bit=1) AS
DECLARE #reply varchar(2048)
... Do a bunch of inserts/updates...
SET #reply = '<xml><big /><outputs /></xml>'
if #doSelect = 1
SELECT #reply
GO
ever tried SET NOCOUNT ON; as an option?