Stored procedure get parameter list and current values - sql

Not sure how to implement this, but I need a way to get the current list of parameters for a stored procedure as well as their passed in values (this code will be executed in the stored procedure itself).
I know I can use sys.parameters to get the parameter names, but how to get the actual values?
What I need to do with this is to make a char string of the form
#param_name1=#value1,#param_name2=#value2,...,#param_namen=#valuen
I have tried to use dynamic sql, but not having much joy with that.
Any ideas??
Edit:
Currently I am just going through all the parameters one-by-one to build the string. However I want a "better" way to do it, since there are quite a few parameters. And incase parameters are added later on (but the code to generate the string is not updated).
I tried using dynamic sql but gave up, since the sp_executesql sp requires parameters be passed into it...

You state '(this code will be executed in the stored procedure itself).' so assuming you are in the procedure you will already know the parameter names as you have to declare them when creating your procedure. Just do a select and put the names inside text fields
ALTER PROCEDURE procname
(
#param1 NVARCHAR(255)
,#param2 INT
...
)
SELECT [Parameters] = '#param1=' + #param1
+ ',#param2=' + CONVERT(NVARCHAR(MAX),#param2)...
The CONVERT is there as an example for non-char datatypes.
update
You will need to create a linked server that points to itself to use the OPENQUERY function.
USE [master]
GO
/****** Object: LinkedServer [.] Script Date: 04/03/2013 16:22:13 ******/
EXEC master.dbo.sp_addlinkedserver #server = N'.', #srvproduct=N'', #provider=N'SQLNCLI', #datasrc=N'.', #provstr=N'Integrated Security=SSPI'
/* For security reasons the linked server remote logins password is changed with ######## */
EXEC master.dbo.sp_addlinkedsrvlogin #rmtsrvname=N'.',#useself=N'True',#locallogin=NULL,#rmtuser=NULL,#rmtpassword=NULL
GO
Now you can do something like this cursor to get each parameter name and then use dynamic sql in OPENQUERY to get the value:
DECLARE curParms CURSOR FOR
SELECT
name
FROM sys.parameters
WHERE OBJECT_ID = OBJECT_ID('schema.procedurename')
ORDER BY parameter_id
OPEN curParms
FETCH curParms INTO #parmName
WHILE ##FETCH_STATUS <> -1
BEGIN
SELECT #parmName + '=' + (SELECT * FROM OPENQUERY('linkedservername','SELECT ' + #parmName))
FETCH curParms INTO #parmName
END
CLOSE curParms
DEALLOCATE curParms

Since SQL Server 2014 we have sys.dm_exec_input_buffer, it is a table valued function with an output column event_info that gives the full execution statement (including parameters).
We can parse the param values from sys.dm_exec_input_buffer and get the param names from sys.parameters and join them together to get the string you want.
For example:
create procedure [dbo].[get_proc_params_demo]
(
#number1 int,
#string1 varchar(50),
#calendar datetime,
#number2 int,
#string2 nvarchar(max)
)
as
begin
-- get the full execution statement
declare #statement nvarchar(max)
select #statement = event_info
from sys.dm_exec_input_buffer(##spid, current_request_id())
-- parse param values from the statement
declare #proc_name varchar(128) = object_name(##procid)
declare #param_idx int = charindex(#proc_name, #statement) + len(#proc_name)
declare #param_len int = len(#statement) - #param_idx
declare #params nvarchar(max) = right(#statement, #param_len)
-- create param values table
select value, row_number() over (order by current_timestamp) seq
into #params
from string_split(#params, ',')
-- get final string
declare #final nvarchar(max)
select #final = isnull(#final + ',','') + p1.name + '=' + ltrim(p2.value)
from sys.parameters p1
left join #params p2 on p2.seq = parameter_id
where object_id = ##procid
select #final params
end
To test it:
exec get_proc_params_demo 42, 'is the answer', '2019-06-19', 123456789, 'another string'
Returns the string you want:
#number1=42,#string1='is the answer',#calendar='2019-06-19',#number2=123456789,#string2='another string'
I have something similar wrapped as a UDF. I use it for error logging in catch blocks.

Related

Does sp_executesql support multiple values in one parameter and return multiple records?

I have created a stored procedure as shown below, but it's returning only one row instead of 3:
CREATE PROCEDURE [dbo].[tempsp]
(#RecycleIds NVARCHAR(MAX) = NULL)
AS
BEGIN
DECLARE #Err INT
DECLARE #WhereClause NVARCHAR(MAX)
DECLARE #SQLText1 NVARCHAR(MAX)
DECLARE #SQLText NVARCHAR(MAX)
SET #SQLText1 = 'SELECT FROM dbo.SKU '
IF #RecycledSkuIds IS NOT NULL
BEGIN
SET #SQLText = 'SELECT FROM dbo.SKU WHERE SKU.SkuId IN (#RecycleIds)'
EXEC sp_executesql #SQLText, N'#RecycleSkuIds nvarchar', #RecycleIds
END
ELSE
BEGIN
EXEC(#SQLText1)
END
SET #Err = ##ERROR
RETURN #Err
END
-------end of stored procedure--------
EXEC tempsp #RecycleIds = '5,6,7'
After running this SQL statement, it only returns one row instead of 3, with the id's of 5, 6, 7.
Can anyone tell me what I am doing wrong?
i wanted to use sp_executesql, so that it can be safe against sql injection with strong type defined.
Use a table type parameter, with a strongly typed column:
CREATE TYPE dbo.IDs AS table (ID int);
GO
CREATE PROCEDURE [dbo].[tempsp] #RecycleIds dbo.IDs READONLY AS
BEGIN
IF EXISTS (SELECT 1 FROM #RecycleIds)
SELECT * --Replace with needed columns
FROM dbo.SKU S
--Using EXISTS in case someone silly puts in the same ID twice.
WHERE EXISTS (SELECT 1
FROM #RecycleIds R
WHERE R.ID = S.SkuID);
ELSE
SELECT * --Replace with needed columns
FROM dbo.SKU S
END;
GO
Then you could execute it like so:
EXEC dbo.tempsp; --All Rows
GO
DECLARE #RecycleIds dbo.IDs;
INSERT INTO #RecycleIds
VALUES(1),(40),(182);
EXEC dbo.tempsp #RecycleIds;
I was trying to retrive the rows whose id matches within the IN clause.
SET #INClauseIds='''' + replace(#Ids, ',', ''',''') + ''''
Above statement would convert the ID's ='1,2,3' to '1','2','3' which i can directly place in the IN clause.
SET #SQLText1 ='EXEC(''SELECT Name,SEOFriendlyName FROM SKU Where Id IN ( ''+ #Ids+'' ) )'
EXEC sp_executesql #SQLText1 ,N'#INClauseIds nvarchar(max)',#Ids=#INClauseIds
If you want to avoid the usage of Temp Table which would add extra caliculation time. you can you the above strategy to retrive n number of records. Safe with strongly coupled with sp_executesql and without any sql injection.
You cannot use IN. Or, more accurately, you have a string and you are confusing it with a list. One method is to instead use LIKE:
SET #SQLText = '
SELECT *
FROM dbo.SKU
WHERE CONCAT('','', #RecycleIds, '','') LIKE CONCAT(''%,'', SKU.SkuId, '',%'')
';

SELECT statement in while loop isn't working

I have created the following Select statement which is working fine:
SELECT #Block = [Blok], #Year = [Aar]
FROM [PT99991_Result].[dbo].[Testheader]
WHERE N = #TestHeaderID
The problem is that this Select statement is used in a While loop where the database can change to another one during the loop. I have tried to modify the statement to the following but it's not working. I have also tried to use EXEC which takes care of my problem but then I'm facing a problem with the local variables #Block and #Year instead.
SET #DataBase = 'PT99991_RESULT' --This is only for test!
SELECT #Block = [Blok], #Year = [Aar]
FROM '[' + #DataBase + '].[dbo].[Testheader]'
WHERE N = #TestHeaderID
I'm not sure what I'm doing wrong?
First, generate a T-SQL template like this:
DECLARE #DynamicTSQLStatement NVARCHAR(MAX) = N'SELECT #Block = [Blok], #Year = [Aar]
FROM [#DataBase].[dbo].[Testheader]
WHERE N = #TestHeaderID';
Then, let's say that the variale #Datbase holds your current database name. If it is not extracted from sys.database you can perform additional validation to ensure nobody is doing something wrong.
IF NOT EXISTS(SELEFT 1 FROM sys.database WHERE [name] = #Datbase
BEGIN
....
END;
After the validation, you just replace the database name in the template:
SET #DynamicTSQLStatement = REPLACE(#DynamicTSQLStatement, '#Database', #Database);
Then, execute the code passing the parameters:
EXEC sp_executesql #DynamicTSQLStatement
,N'#TestHeaderID INT'
,N'#Block INT OUTPUT'
,N'#Year INT OUTPUT'
,#TestHeaderID
,#Block OUTPUT
,#Year OUTPUT;
of course on every loop iteration, reset the template.
Instead of while loop, You can go for undocumented stored procedure: ms_foreachdb and execute against the databases and finally apply filter for the specific database.
Caveat: Don't use this in production code, because, it uses undocumented stored procedure.
CREATE TABLE #test(dbname sysname, blok int, aar int)
DECLARE #db_list NVARCHAR(max) = 'DB1,DB2'
EXECUTE master.sys.sp_MSforeachdb 'USE [?]; declare #blok int, #aar int; INSERT INTO #test SELECT db_name(), blok, aar from [dbo].[Testheader] WHERE N = TestHeaderId;'
SELECT * FROM #test where dbname in
(
SELECT value FROM string_split(#db_list,',')
)

Query saved in table to run in different query's where clause

I have a table that have a list of "Reports" or "Correspondence Letters" (html files). This list will appear in a dropdown in a website. Some of these items will need a "check" to see if they are allowed to be added to the dropdown.
An example of how this table (I removed columns that was not necessary to display):
In this table there is a column "Cor_PolNeedCheck_ToShow". This value links to a different table where the query for that file is stored.
From here I need to create a query that will build the actual list that will be displayed. But in order to do that I need to run the query that is in the second table to perform the check. I've created the following query to do this and it brings back the expected results:
DECLARE #retvalue INT
DECLARE #Paramater NVARCHAR(20) = '241215'
DECLARE #Cor_GroupCde NVARCHAR(10) = 248
DECLARE #Statement NVARCHAR(500);
SELECT #Statement = (SELECT Lookup_Query + #Paramater FROM [dbo].[Ref_Lookup]
WHERE Lookup_ID = (Select Cor_PolNeedCheck_ToShow FROM dbo.Ref_Correspondence WHERE Cor_Group_Cde = #Cor_GroupCde));
EXEC #retvalue = sp_executesql #Statement;
PRINT #retvalue
This value needs to be passed into a query again to verify each record to show or not to show. This is the query that will run to show the items (I have added the #retvalue of the above in the query just to show what it needs to do). This query is in a stored proc where the #Paramater will be passed (above) from the application and then to be used as needed for the below (as above query needs to fit into this one).
SELECT Cor_Group_Cde, Cor_Desc
FROM Ref_Correspondence
WHERE Cor_Show = 'Y' AND Cor_Prod_List Like '%#' + #ProdID + '#%'
AND (Cor_PolNeedCheck_ToShow IS NULL OR --#retValue > 0)
The problem I'm facing is that I need to get the #retValue into the where clause.
I have though of a stored proc, but a stored proc cannot be called in a where clause. I then though of a user defined function, but the problem there is that you cannot call a stored proc ("sp_executesql") in a function.
Due to company standards I cannot do this within the application either. Is there a way around this or is there a way to do this that I maybe missed with the above?
-------------------- EDIT ---------------------
The function I created looks like this:
USE [DBName]
GO
/****** Object: UserDefinedFunction [dbo].[ufn_CorrespondenceCheckResult] Script Date: 4/11/2019 5:35:04 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ufn_CorrespondenceCheckResult]
(
#Paramater nvarchar(20),
#Cor_GroupCde nvarchar(10)
)
RETURNS int
AS
BEGIN
DECLARE #Statement nvarchar(MAX);
DECLARE #Return int;
set #Return = 0;
SELECT #Statement = (SELECT Lookup_Query + #Paramater FROM [dbo].[Ref_Lookup]
WHERE Lookup_ID = (Select Cor_PolNeedCheck_ToShow FROM dbo.Ref_Correspondence WHERE Cor_Group_Cde = #Cor_GroupCde));
EXEC #Return = sp_executesql #Statement;
return #Return;
END
Running the above function in my script gives me an error of "Only functions and some extended stored procedures can be executed from within a function.":
SELECT Cor_Group_Cde, Cor_Desc
FROM Ref_Correspondence
WHERE Cor_Show = 'Y' AND Cor_Prod_List Like '%#' + #ProdID + '#%'
AND (Cor_PolNeedCheck_ToShow IS NULL OR ((SELECT [dbo].[ufn_CorrespondenceCheckResult] (#Paramater,#Cor_GroupCde)) > 0))
It would be better to convert your Stored Procedure into a Function, if possible. Then you can use function in WHERE clause.
If you can't convert it into the function then better to execute the SP and store the full result in a Variable. Now you can use This table in WHERE clause.
You should store the result of the Stored Procedure in a variable in then use that in your next statement's condition. You can use an OUTPUT parameter to return the value of from the Stored Procedure.
DECLARE #count INT
EXEC FindProductByModel --CREATE a stored procedure FindProductByModel instead of a function
#Paramater = '241215',
#Cor_GroupCde = '248',
#retvalue= #count OUTPUT --#retval is an output paramater of your stored procedure
SELECT Cor_Group_Cde, Cor_Desc
FROM Ref_Correspondence
WHERE Cor_Show = 'Y' AND Cor_Prod_List Like '%#' + #ProdID + '#%'
AND (Cor_PolNeedCheck_ToShow IS NULL OR #count>0)

Dynamically get all parameter values in stored procedure

Is there any way to get all parameter values from a stored procedure dynamically?
In other words, iterate through all parameters in one stored procedure to get their values into one string. This is for a unified logging process for a bunch of stored procedures.
I can get the names of parameters:
SELECT PARAMETER_NAME
FROM INFORMATION_SCHEMA.PARAMETER
WHERE SPECIFIC_NAME = 'procedure_name';
Also, I tried to use dynamic SQL commands. I've generated a command with included parameter, but EXEC can't execute command.
#cmd = 'SELECT '#UserID' + CONVERT(NVARCHAR(MAX), #UserID)
+ '#Date' + CONVERT(NVARCHAR(MAX), #Date)'
EXEC #cmd
Is there any way to do this besides manually generating a list of parameter values for each stored procedure?
Since SQL Server 2014 there is sys.dm_exec_input_buffer a table valued function with an output column event_info that gives the full execution statement (including parameters).
I use this for error logging in stored procedures.
For example:
--include this inside the stored procedure
declare #statement nvarchar(max)
select #statement = event_info
from sys.dm_exec_input_buffer(##spid, current_request_id())
--this will print whatever you called the procedure with (including parameters)
print #statement
-- if you want to parse just the parameters from the statement, it can be done like this
declare #proc_name varchar(128) = object_name(##procid)
declare #param_idx int = charindex(#proc_name, #statement) + len(#proc_name)
declare #param_len int = len(#statement) - #param_idx
declare #params nvarchar(max) = right(#statement, #param_len)
select #params

How can I spot in what database is a stored procedure with name 'myStoredProcedure'?

There are bunch of databases to the SQL server I am connected.
How should I query the sysobjects in order to spot in what database a stored procedure with name 'myStoredProcedure' is located ?
The query should return the database name.
Thanks
I know you are not asking for this, but I'd really download RedGate's Sql Search add-in for SSMS and use that. It allows you to find any object (proc, table, view, column, etc) on any database easily.
And it's free!
I'd give this a try:
CREATE TABLE ##DatabaseList
(
DatabaseName varchar(50)
)
EXECUTE SP_MSForEachDB 'USE [?]; INSERT INTO ##DatabaseList SELECT DB_NAME() FROM [sys].[objects] WHERE name = "MyStoredProcedure" AND type_desc = "SQL_STORED_PROCEDURE"'
SELECT * FROM ##DatabaseList
DROP TABLE ##DatabaseList
That's using the undocumented/ unsupported system stored procedure SP_MSForEachDb and writing any hits to a global temp table, then outputting the contents to the Results window before dropping the table. If you just need to know which database (or databases - there may of course be more than one) has an appropriately named SP, this should do it. If you want to use the output elsewhere as a parameter, it may take a little more work.
By the way, I'm only learning this stuff myself over the last few months so if anyone can critique the above and suggest a better way to go at it I'm happy to receive feedback. Equally, I can answer any further questions posted here to the best of my ability.
Cheers
So out of curiosity I decided to try write this myself, especially since ADG mentioned his solution was using an unsupported, undocumented procedure. This could also be expanded to take a 2nd parameter so where it checks the type = P (stored Proc) you could probably change it to look for other things like views / tables etc.
My solution is a bit long but here goes:
CREATE PROCEDURE spFindProceduresInDatabases
(
#ProcedureName NVARCHAR(99)
)
AS
BEGIN
-- Get all the database names and put them into a table
DECLARE #Db TABLE (DatabaseName Varchar(99))
INSERT INTO #Db SELECT name FROM Sys.databases
-- Declare a table to hold our results
DECLARE #results TABLE (DatabaseName VARCHAR(99))
-- Make a Loop
-- Declare a variable to be incremented
DECLARE #count INT
SET #count = 0
-- Declare the end condition
DECLARE #endCount INT
SELECT #endCount = COUNT(*) FROM #Db
-- Loop through the databases
WHILE (#count < #endCount )
BEGIN
-- Get the database we are going to look into
DECLARE #dbWeAreChecking VARCHAR(99)
SELECT TOP 1 #dbWeAreChecking = DatabaseName FROM #Db
DELETE FROM #Db WHERE DatabaseName = #dbWeAreChecking
-- Create and execute our query
DECLARE #Query NVARCHAR(3000)
SET #Query = N'SELECT #outParam = COUNT(*) FROM '+#dbWeAreChecking+'.sys.sysobjects WHERE type = ''P'' and name = #ProcedureName'
Declare #outParam INT
print (#Query)
DECLARE #ParmDefinition NVARCHAR(500)
DECLARE #IntVariable INT
SET #ParmDefinition = N'#ProcedureName VARCHAR(99),#outParam INT OUTPUT'
SET #IntVariable = 35
EXECUTE sp_executesql
#Query ,
#ParmDefinition,
#ProcedureName,
#outParam = #outParam OUTPUT
-- If we have a result insert it into the results table
If (#outParam > 0)
BEGIN
INSERT INTO #results(DatabaseName) VALUES(#dbWeAreChecking)
END
-- Increment the counter
SET #count = (#count + 1)
END
-- SELECT ALL OF THE THINGS!!!
SELECT * FROM #results
END