Generalising an SQL table lookup function using exec - sql

I have the following SQL function:
create function [dbo].[LookUpAnonymiseString](#string varchar(500), #tableSize int)
returns varchar(500)
as
begin
DECLARE #output varchar(500)
SELECT #output = Value FROM AnonymisationLookup.dbo.Forename WHERE AnonymisationLookup.dbo.Forename.ID = ABS(CHECKSUM(#string)) % #tableSize
return #output
end
go
I believe this function works fine, it takes an input string and int representing the size of a look up table (containing all available strings). I then hash the input string into an index to look up the table, returning the value at that index for an output string.
I want to generalise the function so the name of the table can be passed in and used in the query, rather than the hardcoded "Forename" table.
I've tried the following, but SQL complains and says "Only functions and some extended stored procedures can be executed from within a function."
create function [dbo].[LookUpAnonymiseString](#string varchar(500), #tableName varchar(128), #tableSize int)
returns varchar(500)
as
begin
declare #output varchar(500)
declare #sql nvarchar(max)
set #sql = N'select #output = Value from AnonymisationLookup.dbo.'+quotename(#tableName)+' where AnonymisationLookup.dbo.'+quotename(#tableName)+'.ID = abs(checksum(#string)) % #tableSize'
exec sp_executesql #sql, N'#output nvarchar(max) out', #output out
return #output
end
go

Your first query "sort of" works. It only works if there are no gaps in the ids -- and it is pretty easy for ids to have gaps (even identity columns). So a safer method is:
DECLARE #output varchar(500);
SELECT TOP 1 #output = Value
FROM AnonymisationLookup.dbo.Forename f
WHERE F.ID >= ABS(CHECKSUM(#string)) % #tableSize
ORDER BY F.ID;
return #output;
Then, your goal to pass in the table cannot be done in a function (except in a really complicated, abstruse way). Hence, you really cannot do what you want to do. You can use dynamic SQL in a stored procedure, but that cannot be used directly in a function.

Related

Stored procedure returns 0 instead of value

There is a stored procedure that can return top 1 result as
USE [DB]
GO
.....
CREATE PROCEDURE [dbo].[GET]
(#in VARCHAR(10), #Out VARCHAR(10) OUTPUT)
AS
SELECT top 1 #Out = tab.Col
FROM table tab
RETURN
GO
When I call it in main query
DECLARE #output VARCHAR(10)
DECLARE #in VARCHAR(10)
DECLARE #Out VARCHAR(10)
EXECUTE dbo.GET #in = 'table', #Out = #output
It prints #output as 0;
but if I do
EXECUTE dbo.GET #in = 'table', #Out = #Out
And print #out, I get the correct value.
Why could this happen?
I did pass output #Out to pre-defined variable #output
Assuming SQLS due to presence of 'dbo' and sqlserver tag
Your query in the procedure doesn't assign a value to the out parameter (called #out) it assigns to some other variable called #outpk. Resolve the naming mismatch and make them the same
Sqlserver does not support LIMIT. To limit result set size use SELECT TOP 1 *. Using TOP (or any similar result set restrictor) without ORDER BY is illogical. Specify an ORDER BY
In sqlserver, output parameters must be passed with the OUTPUT keyword when calling the procedure:
EXEC sprocname #inputparameter ='value', #outputparameter = #variableToSet OUTPUT;
Use semicolons; omitting them is deprecated
Example
USE [DB]
GO
CREATE PROCEDURE [dbo].[GET]
(#in VARCHAR(10), #OutPk VARCHAR(10) OUTPUT)
AS
SELECT #OutPK = tab.Col
FROM table tab
ORDER BY tab.Col;
GO
DECLARE #output VARCHAR(10);
EXECUTE dbo.GET #in = 'table', #OutPK = #output OUTPUT
SELECT #output;
If its MySql (Limit is in mySql), you can simply call:
Call dbo.GET('table', #out);
No need to have separate variable #output.

Stored procedure get parameter list and current values

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.

Getting an error when executing a dynamic sql within a function (SQL Server)?

I create a function to execute dynamic SQL and return a value. I am getting "Only functions and some extended stored procedures can be executed from within a function." as an error.
The function:
Create Function fn_GetPrePopValue(#paramterValue nvarchar(100))
returns int as
begin
declare #value nvarchar(500);
Set #SQLString = 'Select Grant_Nr From Grant_Master where grant_id=' + #paramterValue
exec sp_executesql
#query = #SQLString,
#value = #value output
return #value
end
The execution:
Select dbo.fn_GetPrePopValue('10002618') from Questions Where QuestionID=114
and:
Select fn_GetPrePopValue('10002618') from Questions Where QuestionID=114
Is the function being called properly or is the function incorrect?
You cannot use dynamic SQL from a function, neither can you call
stored procedures.
Create proc GetPrePopValue(#paramterValue nvarchar(100))
as
begin
declare #value nvarchar(500),
#SQLString nvarchar(4000)
Set #SQLString = 'Select #value = Grant_Nr From Grant_Master where grant_id = #paramterValue'
exec sp_executesql #SQLString, N'#paramterValue nvarchar(100)',
#paramterValue,
#value = #value output
return #value
end
Functions are limited in what they can use, so that you can use them in a query without accidentally make something that would give horrible performance. Using dynamic queries is one of those things, as that would cause a query planning for each execution, and also would keep the function from being able to be part of a query plan.
You don't need the dynamic query at all in this case, just return the value:
Create Function fn_GetPrePopValue(#paramterValue nvarchar(100))
returns int as
begin
return (select Grant_Nr From Grant_Master where grant_id = #paramterValue)
end
I don't think you can use dynamic SQL from a function, nor do I think you need to in your case. Looks like you want something closer to this:
Create Function dbo.fn_GetPrePopValue(#paramterValue nvarchar(100))
returns int as
begin
declare #value int
declare #SQLString varchar(MAX)
Select #value=Grant_Nr From Grant_Master where grant_id=#paramterValue
return #value
end
SQL Fiddle Demo
Also, please check your data types to make sure you're fields are correct. Seems odd to pass in a varchar for the id and return an int for the other field. Either way, this should help you get going in the right direction.

Is it possible to insert a column as a parameter in a user-defined function?

I'm trying to figure out if it's possible to insert a column as a parameter in a user-defined function.
What I want to do is insert a column from a certain table and then return the minimum of that column when I call the function.
(For my purposes it's not the actual minimum that I want, I just want it to return one number, whether it be min, max, variance, standard deviation, or any other mathematical equation that gives me one scalar value).
You will need to use dynamic SQL using sp_executesql
CREATE procedure dbo.[myfunc](#COL NVARCHAR(20),
#result int output)
AS
BEGIN
DECLARE #SQLString nvarchar(500)
DECLARE #ParmDefinition nvarchar(500)
SET #SQLString = N'SELECT #max_valueOUT = max(' + #COL + ') FROM mytable'
SET #ParmDefinition = N'#max_valueOUT int OUTPUT'
EXECUTE sp_executesql #SQLString, #ParmDefinition, #max_valueOUT=#result OUTPUT
END
SQL Fiddle
I've done this before. you have to create a User Defined Data Type. Then pass the user defined type into the function.

Generic SQL to get max value given table and column names as varchar

it is very easy to use the following SQL to get value for a specific primary key: ID from a specific table: myTale:
DECLARE #v_maxID bigint;
SELECT #v_maxID = MAX(ID) FROM myTable;
What I need is a generic SQL codes to get the max value for a key from a table, where both key and table are specified as varchar(max) types as parameters:
DECLARE #v_maxID bigint;
-- SELECT #v_maxID = MAX(#p_ID) FROM #p_Table;
I comment out the SELECT since it is not working. I tried to build a SQL string and I can EXEC it, but I cannot get the max value back to my local variable(#v_maxID). Any suggestions?
DECLARE #max bigint, #sql nvarchar(max)
SET #sql = N'SELECT #max = MAX(' + #p_ID + ') FROM ' + #p_Table
EXEC sp_executesql
#query = #sql,
#params = N'#max bigint OUTPUT',
#max = #max OUTPUT
PRINT #max
Users are choosers, but I consider this an ugly idea (for being overgeneralized). And unoptimizable. Just write the SQL.
Correct me if I'm wrong, but don't you just want:
SELECT MAX(ID) FROM mytable
Just build the query at the app level, thus the query running would be just like the one above. Doing in on sql will certainly open you for sql injection, since you have to use exec(). Also in either case, be careful with user input.
As BC states, you have to use sp_executesql with an OUTPUT parameter.
How to specify output parameters when you use the sp_executesql stored procedure in SQL Server