SQL Server: user defined function parameter - sql

I was wondering if it is possible to create a user-defined function that can take a parameter of varying length (as well as other parameters).
Essentially I want to pass a parameter that then gets used in an IN statement. What I would like to do is have something like this:
CREATE FUNCTION ufnGetOpenNotificationValue
(#LegacyTypeID INT,
#MonthEnd DATE,
#YearStart DATE,
#YearEnd DATE)
Where #LegacyTypeID is a list of integers.
So then using the function might look something like this
SELECT RE_Auxiliary.dbo.ufnGetOpenNotificationValue
((1,2,3),'2014-07-31','2013-09-01','2014-08-31')
rather than
SELECT RE_Auxiliary.dbo.ufnGetOpenNotificationValue
(1,'2014-07-31','2013-09-01','2014-08-31') +
RE_Auxiliary.dbo.ufnGetOpenNotificationValue
(2,'2014-07-31','2013-09-01','2014-08-31') +
RE_Auxiliary.dbo.ufnGetOpenNotificationValue
(3,'2014-07-31','2013-09-01','2014-08-31')
but if I try and pass multiple integers I get an error stating
Incorrect syntax near ','

As Alex K states you can't pass arrays as an input for a SQL function. You can pass table types (Pass table as parameter).
CREATE TYPE TableType
AS TABLE (LegacyTypeID INT)
CREATE FUNCTION ufnGetOpenNotificationValue
(#LegacyTypeID TableType,
#MonthEnd DATE,
#YearStart DATE,
#YearEnd DATE)
...
WHERE COLUMN_NAME IN (SELECT LegacyType FROM #LegacyTypeID)
You would then need to insert into a TableType variable before calling your function with that variable passed as a parameter
Another option would be to pass in your list in as a comma separated list of values. And then use a function (like this one) to use in your where clause
CREATE FUNCTION ufnGetOpenNotificationValue
(#LegacyTypeID NVARCHAR(256),
#MonthEnd DATE,
#YearStart DATE,
#YearEnd DATE)
...
WHERE COLUMN_NAME in (SELECT val FROM dbo.f_split(#StringParameter, ','))
Which you could then call like this:
SELECT RE_Auxiliary.dbo.ufnGetOpenNotificationValue
('1,2,3','2014-07-31','2013-09-01','2014-08-31')

Related

To Create a Function to split dates into Year, Month, Date into a separate column in SQL

Trying to create a function to split dateformat of "2018-05-21" to 2018 | 05 | 21 | as three separate columns. Tried creating the function as below but gives me error on "month", "Day". Error says "incorrect syntax near 'month'. Expecting '(' or Select."
CREATE FUNCTION [dbo].[functionname]
(
-- Add the parameters for the function here
#DateFormat AS DATETIME
)
RETURNS VARCHAR (MAX)
AS
BEGIN
RETURN DATEPART(YEAR,#DateFormat),
DATEPART(Month,#DateFormat),
DATEPART(Day,#DateFormat)
END
GO
The problem with your current SQL is that a scalar only returns a single value. You need to use a table value function to get multiple columns.
This is a TVF version which will provide three columns
CREATE FUNCTION [dbo].[FunctionName]
(
#DateFormat AS DATETIME
)
RETURNS TABLE AS RETURN
(
SELECT DATEPART(YEAR,#DateFormat) AS [Year],
DATEPART(Month,#DateFormat) AS [Month],
DATEPART(Day,#DateFormat) AS [Day]
)
Example usage:
DECLARE #dates TABLE (SomeDate DATE)
INSERT INTO #dates SELECT '01/25/2018'
INSERT INTO #dates SELECT '10/01/2008'
SELECT d.*,fn.* FROM #dates d
CROSS APPLY [dbo].[FunctionName](d.SomeDate) fn
And some documentation.
That said, I personally don't like this implementation. I would simply expect the DATEPART statements in the SELECT portion of the SQL. I think the TVF makes it more complicated and doesn't provide any tangible benefits.

Escape SQL function string parameter within query

I have a SQL view that calls a scalar function with a string parameter. The problem is that the string occasionally has special characters which causes the function to fail.
The view query looks like this:
SELECT TOP (100) PERCENT
Id, Name, StartDate, EndDate
,dbo.[fnGetRelatedInfo] (Name) as Information
FROM dbo.Session
The function looks like this:
ALTER FUNCTION [dbo].[fnGetRelatedInfo]( #Name varchar(50) )
RETURNS varchar(200)
AS
BEGIN
DECLARE #Result varchar(200)
SELECT #Result = ''
SELECT #Result = #Result + Info + CHAR(13)+CHAR(10)
FROM [SessionInfo]
WHERE SessionName = #Name
RETURN #Result
END
How do I escape the name value so it will work when passed to the function?
I am guessing that the problem is non-unicode characters in dbo.Session.Name. Since the parameter to the function is VARCHAR, it will only hold unicode characters, so the non-unicode characters are lost when being passed to the function. The solution for this would be to change the parameter to be NVARCHAR(50).
However, if you care about performance, and more importantly consistent, reliable results stop using this function immediately. Alter your view to simply be:
SELECT s.ID,
s.Name,
s.StartDate,
s.EndDate,
( SELECT si.Info + CHAR(13)+CHAR(10)
FROM SessionInfo AS si
WHERE si.SessionName = s.Name
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') AS Information
FROM dbo.Session AS s;
Using variable concatenation can lead to unexpected results which are dependent on the internal pathways of the execution plan. So I would rule this out as a solution immediately. Not only this, the RBAR nature of a scalar UDF means that this will not scale well at all.
Various ways of doing this grouped concatenation have been benchmarked here, where CLR is actually the winner, but this is not always an option.

Passing multiple values to a parameter of a function in SQL

There is function Getfunctionname(userid, startdate, enddate) to return a table
My question is can I pass a variable with multiple values?
i.e.
getfunctionname(#userid, startdate, enddate)
Where the value of variable #userid is like
1
2
3
4
5
(actually using split function splitting the values from being 1,2,3,4,5 )
If I can please let me know
One way of doing that which I prefer is to make a new user-defined table data type.
CREATE TYPE [dbo].[IdList] AS TABLE(
[Id] [int] NULL
)
Then you can use that data type as one of the parameters
CREATE FUNCTION Getfunctionname
(
#UserIDs dbo.IdList READONLY,
#startdate INT,
#endtdate INT
)
RETURNS #ReturnTable TABLE
(
-- ReturnTable
)
AS
BEGIN
-- Query
RETURN
END
Use the concept of CSV
CREATE FUNCTION [dbo].[uspGetNumbers]
userid,startdate,enddate // define your paramters the way you want
AS
BEGIN
// your code
JOIN dbo.fnSplit(#UserIDs, ',')
END
GO
Example function:
SELECT [dbo].[uspGetNumbers] '1,2,3,4,5', '', ''
I just ran into this, and I used the CROSS APPLY solution from this post:
SQL Server: run function for each row based on the provided row value
To use CROSS APPLY, you would need to first select your values, and then CROSS APPLY. I have not used the split function before, so I don't have the exact syntax,
but if you use it something like:
select #userid, F1.* from split(1,2,3,4,5),
CROSS APPLY getfunctionname(#userid, startdate, enddate) F1

Use an SSRS multi-value parameter in a SQL query without using the IN operator

I have to pass multiple values (eg: Year1, year2, year3, etc.) to the same query, but I cannot use the IN condition as I'm using less than or equal to in most of the cases. Can I do this by passing multiple values through the same parameter without changing the query?
Is it possible to get multiple values from an SSRS parameter and pass them on to the query to get the output as:
Year1 Year2 Year3
Value(output) Value(output) Value(output)
You can pass the multi-value parameter as a comma separated string but your SQL query is going to need updating to handle that CSV string. In order to pass the multi-value parameter as a CSV string you would open the dataset properties and go to the parameters tab. Then set the value of your parameter to this expresssion:
=JOIN(Parameters!MultiValueYearParameter.Value,",")
This will join all of the values in the multivalue parameter together and use a comma as the delimiter. You can then process this using the split function below (or just modify it to work inline in your SQL if you either cannot or don't need to create a separate function to do this).
This blog post on a Split Function for T-SQL using FOR XML shows how to do this without using string parsing or a while loop. String parsing is prone to error and isn't scalable and while loops should just be avoided in SQL whenever possible.
Below I've modified the split function to return a table of DATE values that you can then use in an INNER JOIN to filter your query using whatever operators you like.
--this is the parameter passed from the report
--(the date strings may not be formatted this way. do not try to rely on that)
DECLARE #YearParameter VARCHAR(MAX) = '2014-01-01,2011-12-02,2015-10-22';
--use this to do the xml parsing
declare #xml xml = N'<root><r>' + replace(#YearParameter,',','</r><r>') + '</r></root>'
--create a table variable to store the date values
DECLARE #dateValues TABLE (val DATE);
--parse the xml/csv string and cast the results to a DATE and insert into the table var
INSERT INTO #dateValues
select CAST(r.value('.','varchar(max)') AS DATE) AS val
from #xml.nodes('//root/r') as records(r);
You can then use that table variable to filter your SQL query. I've given an example of how to use it below.
In the example I create a table of rows with a start and end date. Then I filter that table to show only rows where a parameter value is between the start and end date.
DECLARE #testTable TABLE (Descript VARCHAR(25), startDate DATE, endDate DATE);
INSERT INTO #testTable (Descript, startDate, endDate)
VALUES ('row1', '2014-05-01','2014-08-01'), ('row2', '2013-10-01','2014-01-10'), ('row3', '2015-10-01','2015-12-15'),('row4','2013-01-01','2015-01-01'),
--these rows won't appear in the result set
('row5','2010-01-01','2010-06-01'), ('row6','2013-12-25','2014-05-20');
-- get all rows from the test table where a selected parameter value
-- is between the start and end dates.
SELECT *
FROM #testTable AS tbl
WHERE EXISTS (
SELECT *
FROM #dateValues
WHERE val BETWEEN tbl.startDate AND tbl.endDate
);
In SSRS you can build tables and complex solutions.
In the Text Query of report builder here is an example of splitting apart a parameter to get three dates.
BEGIN
/* suppose the inbound pram is a string with 10 places per date '01/01/2010,11/12/2012,05/06/2013' */
/* you could also nip the string apart by each comma... */
DECLARE #YEAR1 DATETIME
DECLARE #YEAR2 DATETIME
DECLARE #YEAR3 DATETIME
SET #YEAR1 = CAST(SUBSTRING(#INBOUNDPARAM, 1, 10) AS DATETIME)
SET #YEAR2 = CAST(SUBSTRING(#INBOUNDPARAM, 12, 10) AS DATETIME)
SET #YEAR3 = CAST(SUBSTRING(#INBOUNDPARAM, 23, 10) AS DATETIME)
SELECT #YEAR1 AS Year1, #YEAR2 AS Year2, #YEAR3 AS Year3
END
Of course the Year of the date is just YEAR(#Year1) = 2010 for example...

sql server - passing unquoted constants to functions like DATEPART does

i would like to create a function which accepts a constant like
the datepart function accepts yy/mm/dd/hh/
like:
select datepart(dd, getdate())
i would like to create my own function that accepts dd
not char like 'dd'
i want
select MyFunc(dd, getdate())
and not
select MyFunc('dd', getdate())
You can't really constrain the input of a UDF to a small set of values (to the best of my knowledge).
I would recommend creating a tabe for your enumerated values - something like this:
CREATE TABLE MyEnumTable (DatePartID tinyint, DatePartValue char(2))
GO
INSERT MyEnumTable(DatePartID, DatePartValue)
SELECT 1, 'yy'
UNION
SELECT 2, 'mm'
UNION
SELECT 3, 'dd'
UNION
SELECT 4, 'hh'
GO
CREATE FUNCTION MyDatePart(#IntervalType tinyint)
RETURNS varchar(255)
AS
BEGIN
IF NOT EXISTS (SELECT * FROM MyEnumTable WHERE DatePartID = #IntervalType)
RETURN 'Invalid IntervalType'
--Do your stuff
DECLARE #DatePartvalue char(2)
SELECT #DatePartValue = DatePartValue
FROM MyEnumTable
WHERE DatePartID = #IntervalType
RETURN #DatePartValue
END
GO
--Check it out
SELECT dbo.MyDatePart(3), dbo.MyDatePart(12)
Of course, my example is oversimplified, but you get the idea.
Also, consider making the function a table-valued function for performance reasons, if you're planning on using the udf in set statements. I blogged about the performance implications of various function types here:
http://thehobt.blogspot.com/2009/02/scalar-functions-vs-table-valued.html
i would like to create my own function that accepts dd not char like 'dd'
I think you're out of luck on this. If you don't single-quote the characters, they'll be interpreted as a name--but you can't have defined them as a name anywhere if you want them to be used in the manner you propose.