Multiple values in a single parameter of a scalar function - sql

Is there a way to input multiple values in a single parameter of a scalar-valued function in SQL Server 2008 R2 and have it filter data by that parameter using both values?
For example I would like to do the following
SET #Salesperson='BILL' OR 'MOSES'
SELECT Sum(SalesDollars)
FROM Invoices
WHERE Invoices.Salesperson = #Salesperson
I attempted to use the following as the WHERE clause, but this didnt work either.
SET #Salesperson='BILL','MOSES'
SELECT Sum(SalesDollars)
FROM Invoices
WHERE Invoices.Salesperson IN (#Salesperson)
Would it be easier if i were dealing with integers as opposed to varchar values?
Any help would be absolutely appreciated!

You need to use table-valued parameters. Look it up on technet or msdn
Best part of it that your table-valued parameters can have multiple columns.
Note however that you have to define TVP parameter as readonly. So if you want to return similar set from your function you will need to create another variable inside your function.
Example:
CREATE TYPE Names AS TABLE
( Name VARCHAR(50));
GO
/* Create a procedure to receive data for the table-valued parameter. */
CREATE PROCEDURE dbo.mySP
#n Names READONLY
AS
SELECT Sum(SalesDollars)
FROM
WHERE Invoices.Salesperson in (select Name from #n)
GO
CREATE FUNCTION dbo.myFun(#n Names READONLY) returns int
AS
SELECT Sum(SalesDollars)
FROM
WHERE Invoices.Salesperson in (select Name from #n)
GO
/* Declare a variable that references the type. */
DECLARE #names AS Names;
/* Add data to the table variable. */
INSERT INTO #names (Name)
VALUES ('BILL'),('MOSES')
-- using stored procedure with TVP
EXEC dbo.mySP #names
-- using function with TVP
select dbo.myFun(#names)
GO

This could be done this way:
SET #Salesperson='BILL,MOSES'
SELECT *
FROM YourTable
WHERE Invoices.Salesperson IN (SELECT * FROM dbo.split(#Salesperson,','))
This is how you split the values.

I would typically do this using a user defined table type: SQL Fiddle Example.
CREATE TYPE <schema>.SalespersonList AS TABLE
(
Name varchar(32)
)
You may have to grant execute permissions on the type:
GRANT EXECUTE ON TYPE::<schema>.SalespersonList TO <user>
Then you can create a function to use it:
CREATE FUNCTION <schema>.fnGetTotalSales
(
#nameList <schema>.SalespersonList READONLY
)
RETURNS INT
AS
BEGIN
DECLARE #ret INT
SELECT #ret = Sum(SalesDollars)
FROM Invoices i
INNER JOIN #nameList nl ON nl.Name = i.Salesperson
RETURN #ret
END
Then you would just insert your list into the type and call the function:
DECLARE #salesPersonList <schema>.SalespersonList
INSERT INTO #salesPersonList (Name)
SELECT 'Bill'
UNION
SELECT 'Moses'
SELECT <schema>.fnGetTotalSales(#salesPersonList)

Related

What is the simplest way to put pass an array of values into a parameter of TVF

Objective:
I would like to have a parameter in my function to allow the user to input a list of values. Ideally, the simplest solution ... Note: I dont have permissions to create tables in dbs.
Situation:
CREATE FUNCTION dbo.fnExample
(
#StartDate AS Date -- Parameter 1
#ParameterArray AS ... -- This would be the parameter that accepts a list of values
)
RETURNS TABLE
AS
RETURN
...code...
GO
Not sure how to pass several parameters in a TVF but this is how I would view the solution
SELECT *
FROM dbo.fnExample ('2019-01-01') and ([list of values])
Using a table type parameter, you can do something like the following:
CREATE TYPE dbo.SomeArray AS TABLE (SomeInt int); --Create the TYPE
GO
--Now the Function
CREATE FUNCTION dbo.Example (#StartDate date, #Array dbo.SomeArray READONLY)
RETURNS TABLE
AS RETURN
SELECT DATEADD(DAY, SomeInt, #StartDate) AS NewDate
FROM #Array
GO
--Now to test
--Declare the TYPE
DECLARE #Array dbo.SomeArray;
--Insert the data
INSERT INTO #Array (SomeInt)
VALUES(7),(1654),(13);
--Test the function
SELECT *
FROM dbo.Example(GETDATE(), #Array) E;
GO
--Clean up
DROP FUNCTION dbo.Example;
DROP TYPE dbo.SomeArray;
How to JOIN:
FROM dbo.YourTable YT
JOIN #Array A ON YT.SomeInt = A.SomeInt
How to use EXISTS:
WHERE EXISTS (SELECT 1 FROM #Array A WHERE YT.SomeInt = A.SomeInt)
If you're on SQL Server 2016+ the simplest way is to use JSON:
CREATE FUNCTION dbo.fnExample
(
#StartDate AS Date -- Parameter 1
#ParameterArray AS NVARCHAR(MAX) -- pass JSON Array like '[1,2,3,4,5]'
)
RETURNS TABLE
AS
RETURN
SELECT TRY_CONVERT(INT,j.value) AS [ID] FROM OPENJSON(#ParameterArray) j;
GO

How to store multiple values in a SQL Server variable

I want to store values from a SELECT statement into a variable which is capable of holding more than one value because my SELECT statement returns multiple values of type INT. This is how my SP looks like so far.
ALTER PROCEDURE "ESG"."SP_ADD"
AS
BEGIN
DECLARE #Id table(identifiers VARCHAR);
INSERT INTO #Id (identifiers) VALUES('axaa1aaa-aaaa-a5aa-aaaa-aa8aaaa9aaaa');
INSERT INTO #Id (identifiers) VALUES('bxbb1bbb-bbbb-b5bb-bbb4-bb8bbbb9bbbf');
DECLARE #tranID INT = (SELECT
DOCUMENT_SET_.DOCUMENT_SET_TRANSACTION_ID
FROM DOCUMENT_SET_TRANSACTION
WHERE DOCUMENT_SET_TRANSACTION.IDENTIFIER IN (SELECT identifiers FROM #Id));
END
Variable #tranID should be a list or an array to hold the ids. Is it possible to do it SQL Server?
You can declare a variable of type table
DECLARE #tblTrans TABLE (
tranID INT
);
INSERT INTO #tblTrans
SELECT DOCUMENT_SET_TRANSACTION.DOCUMENT_SET_TRANSACTION_ID
FROM ESG.DOCUMENT_SET_TRANSACTION
WHERE DOCUMENT_SET_TRANSACTION.IDENTIFIER
IN (SELECT identifiers FROM #envelopeId);
Depending on what you want to do with the values after this, you could declare a cursor to loop through them or select straight from the variable.
You could also look into using a temporary table depending on what scope you need.
Try this, only take the firs row of example. Do u try this?
select DOCUMENT_SET_TRANSACTION.DOCUMENT_SET_TRANSACTION_ID,
(STUFF((SELECT '-' + convert(varchar(max),DOCUMENT_SET_TRANSACTION.DOCUMENT_SET_TRANSACTION_ID)
FROM ESG.DOCUMENT_SET_TRANSACTION
FOR XML PATH ('')), 1, 2, '')) AS example
FROM ESG.DOCUMENT_SET_TRANSACTION

Returning a table from a function with parameters in SQL

As far as I know, we can return a table as a result of a db function:
CREATE FUNCTION MyFunction(#Value varchar(100))
RETURNS table
AS RETURN (select * from MyTable where ColumnName = '#Value')
in this example we can make the column name as a parameter for the function. My question is, can we write the column name and table name as a parameter for the function? hence we can write a more generic function something like:
CREATE FUNCTION MyGenericSearchFunction(#TableName varchar(100), #ColumnName varchar(100), #Value varchar(100))
RETURNS table
AS RETURN (select * from #TableName where #ColumnName = '#Value')
No, you can't.
This would then be a dynamic query.
For dynamic queries in SQL Server, one has to use exec() or sp_executesql() functions, which are not allowed in functions.

How to pass udtt individually to another function in SQL

Suppose I want to get student summaries from a two tables: student, grade:
CREATE PROCEDURE prc_get_student_grade_summaries
#studentIds [Integer_udtt] READONLY
AS
BEGIN
SELECT Name,
func_GetGradeAForStudent()
FROM tbl_student AS tS
INNER JOIN
#studentIds AS tSI
ON tS.Id = tSI.studentId
Integer udtt is defined as this:
CREATE TYPE [Integer_udtt] AS TABLE (
[Id] INT NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC));
func_GetGradeAForStudent is something like select COUNT(*) from student where studentId = id AND grade = 1 -- 1: A
The result I want is list of summary:
StudentId Number of A
101 5
102 4
103 2
What is the correct way to pass in the studentId from #studentIds to the func_GetGradeAForStudent?
You can't pass table-value parameters to UDF's:
MSDN:
Limitations of Table-Valued Parameters
There are several limitations to table-valued parameters:
You cannot pass table-valued parameters to user-defined functions.
Table-valued parameters can only be indexed to support UNIQUE or PRIMARY KEY constraints. SQL Server does not maintain statistics on
table-valued parameters.
Table-valued parameters are read-only in Transact-SQL code. You cannot update the column values in the rows of a table-valued
parameter and you cannot insert or delete rows. To modify the data
that is passed to a stored procedure or parameterized statement in
table-valued parameter, you must insert the data into a temporary
table or into a table variable.
You cannot use ALTER TABLE statements to modify the design of table-valued parameters.
Since these are just a list of student Ids, one possibility would be to pass those Ids as a comma-separeted list of ids and use a split function to recreate a table from the comma-separeted list. There's a ton of examples here on SO and elsewhere where you can find sample implementation of a split function.
Or even better, do everything inside your proc. I don't really see the need for that function if all is doing is a select count(*)... You should be able to do everything inline, perhaps using a subselect as so:
CREATE PROCEDURE prc_get_student_grade_summaries
#studentIds [Integer_udtt] READONLY
AS
BEGIN
SELECT Name,
(select COUNT(*) from student where studentId = tSI.studentId AND grade = 1 ) as NumberOfAs
FROM tbl_student AS tS
INNER JOIN
#studentIds AS tSI
ON tS.Id = tSI.studentId
UPDATE
Sample split function that takes a comma-separated list and returns a table:
CREATE FUNCTION [dbo].[fnSplit](
#sInputList VARCHAR(8000) -- List of delimited items
, #sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS #List TABLE (item VARCHAR(8000))
BEGIN
DECLARE #sItem VARCHAR(8000)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT
#sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))),
#sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
And you call it like this: select * from fnSplit(#CommaSeparetedList,',');

T-SQL Foreach Loop

Scenario
I have a stored procedure written in T-Sql using SQL Server 2005.
"SEL_ValuesByAssetName"
It accepts a unique string "AssetName".
It returns a table of values.
Question
Instead of calling the stored procedure multiple times and having to make a database call everytime I do this, I want to create another stored procedure that accepts a list of all the "AssetNames", and calls the stored procedure "SEL_ValueByAssetName" for each assetname in the list, and then returns the ENTIRE TABLE OF VALUES.
Pseudo Code
foreach(value in #AllAssetsList)
{
#AssetName = value
SEL_ValueByAssetName(#AssetName)
UPDATE #TempTable
}
How would I go about doing this?
It will look quite crippled with using Stored Procedures. But can you use Table-Valued Functions instead?
In case of Table-Valued functions it would look something like:
SELECT al.Value AS AssetName, av.* FROM #AllAssetsList AS al
CROSS APPLY SEL_ValuesByAssetName(al.Value) AS av
Sample implementation:
First of all, we need to create a Table-Valued Parameter type:
CREATE TYPE [dbo].[tvpStringTable] AS TABLE(Value varchar(max) NOT NULL)
Then, we need a function to get a value of a specific asset:
CREATE FUNCTION [dbo].[tvfGetAssetValue]
(
#assetName varchar(max)
)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
SELECT 0 AS AssetValue
UNION
SELECT 5 AS AssetValue
UNION
SELECT 7 AS AssetValue
)
Next, a function to return a list AssetName, AssetValue for assets list:
CREATE FUNCTION [dbo].[tvfGetAllAssets]
(
#assetsList tvpStringTable READONLY
)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
SELECT al.Value AS AssetName, av.AssetValue FROM #assetsList al
CROSS APPLY tvfGetAssetValue(al.Value) AS av
)
Finally, we can test it:
DECLARE #names tvpStringTable
INSERT INTO #names VALUES ('name1'), ('name2'), ('name3')
SELECT * FROM [Test].[dbo].[tvfGetAllAssets] (#names)
In MSSQL 2000 I would make #allAssetsList a Varchar comma separated values list. (and keep in mind that maximum length is 8000)
I would create a temporary table in the memory, parse this string and insert into that table, then do a simple query with the condition where assetName in (select assetName from #tempTable)
I wrote about MSSQL 2000 because I am not sure whether MSSQL 2005 has some new data type like an array that can be passed as a literal to the SP.