Table-valued function that takes table as parameter and performs join - sql

I use SQL server 2016.
I want to write a function that takes a table as a parameter and then performs a join on that table with another table.
I have declared the following type:
CREATE TYPE WorklistTable AS TABLE (WorklistId int NOT NULL)
Then I use it in a lot of functions that do selects based on certain conditions
CREATE FUNCTION [dbo].[fnGetSomeData] (
#WorklistIds WorklistTable readonly
)
RETURNS TABLE
AS RETURN
(
select WorklistId, wlu.UserId
from #WorklistIds
join [dbo].[WorklistUser] wlu on wlu.WorklistId = #WorklistIds.worklistId
-- the rest is omitted
);
I get the following error:
Must declare the scalar variable "#WorklistIds".
I tried to declare the variable, but I got an error:
The variable name '#WorklistIds' has already been declared. Variable names must be unique within a query batch or stored procedure.

You should use aliases when you are joing to table variable.
CREATE FUNCTION [dbo].[fnGetSomeData] (
#WorklistIds WorklistTable readonly
)
RETURNS TABLE
AS RETURN
(
select WorklistId, wlu.UserId
from #WorklistIds t
join [dbo].[WorklistUser] wlu on wlu.WorklistId = t.worklistId
-- the rest is omitted
);

You can't directly use the #Table name when referencing a column within a table variable. You either need to alias the table or wrap it in square brackets:
select WorklistId, wlu.UserId
from #WorklistIds As W
join [dbo].[WorklistUser] wlu on wlu.WorklistId = W.worklistId
Or
select WorklistId, wlu.UserId
from #WorklistIds
join [dbo].[WorklistUser] wlu on wlu.WorklistId = [#WorklistIds].worklistId

Related

Return table valued function return table structure like my actual table using Sql Server 2008?

I have a table named tableincentive like below
create table tableincentive (entry_date date,ag_id int,us_id int,lo_id int,loin_id int,de_id int,dest_id int,incentive_amt decimal(18,2),le_id int,lion_id int,etc...)
And i have a table valued function named [dbo].[func_getdetails]
Create FUNCTION [dbo].[func_getdetails]
(
-- Add the parameters for the function here
#ason_date date
)
RETURNS
#tableincentive TABLE
(
-- Add the column definitions for the TABLE variable here
entry_date date,ag_id int,us_id int,lo_id int,loin_id int,de_id int,dest_id int,incentive_amt decimal(18,2),le_id int,lion_id int,etc...
)
AS
BEGIN
-- some long process then
insert into #tableincentive
select * from tableincentive;
end
My problem is the table tableincentive has 43 columns so i create a table valued function return tableincentive structure.I need return like my actual table.
that means in function
RETURNS
#tableincentive TABLE
(
-- Add the column definitions for the TABLE variable here
entry_date date,ag_id int,us_id int,lo_id int,loin_id int,de_id int,dest_id int,incentive_amt decimal(18,2),le_id int,lion_id int,etc...
)
AS
begin end
changed to
RETURNS tableincentive
AS
begin
end
or
RETURNS #tableincentive table like tableincentive
AS
begin
end
User Defined Functions need Static results. That means you have to define the return type while creating the function. So If you want to have a static result set, then probably you should opt for a stores Procedure.

MS Sql Server - Function not compiling because of selecting from table

In SQL Server, I made a function that will return the sum of the column from a table which is the result of a query to another table. It's easier to understand when looking at the code:
CREATE FUNCTION [dbo].[getPatientMorphineEquivalentDose]
(
#patientID int
)
RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
RETURN (SELECT SUM(j.MilligramMorphineEquivalent) FROM
(
SELECT i.Mg * i.[Morphine Equivalent (mg)] AS MilligramMorphineEquivalent
FROM
(
SELECT PatientMedication.Mg, Medication.[Morphine Equivalent (mg)]
FROM PatientMedication
INNER JOIN Medication
ON PatientMedication.MedicationID = Medication.Id
WHERE PatientMedication.PatientID = #patientID
) AS i
) AS j )
END
I have very little experience with Sql Server so I am not sure if I am doing anything wrong, but from what I researched online this should work. I tried it with a stored procedure as well and it still would not compile.
You didn't specify which error you got, but since you're defining your function with schemabinding, I expect you got an error like:
Cannot schema bind function 'dbo.getPatientMorphineEquivalentDose' because name 'PatientMedication' is invalid for schema binding. Names must be in two-part format and an object cannot reference itself.
When you use schemabinding, you are expected to prefix the object names with the owner. Notice what the documentation says (emphasis mine):
A function can be schema bound only if the following conditions are true:
The function is a Transact-SQL function.
The user-defined functions and views referenced by the function are also schema-bound.
The objects referenced by the function are referenced using a two-part name.
The function and the objects it references belong to the same database.
The user who executed the CREATE FUNCTION statement has REFERENCES permission on the database objects that the function references.
So assuming your tables are owned by dbo, make sure to prefix the 2 referenced tables in the query (dbo.PatientMedication and dbo.Medication):
CREATE FUNCTION [dbo].[getPatientMorphineEquivalentDose]
(
#patientID int
)
RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
RETURN (SELECT SUM(j.MilligramMorphineEquivalent) FROM
(
SELECT i.Mg * i.[Morphine Equivalent (mg)] AS MilligramMorphineEquivalent
FROM
(
SELECT PatientMedication.Mg, Medication.[Morphine Equivalent (mg)]
FROM dbo.PatientMedication
INNER JOIN dbo.Medication
ON PatientMedication.MedicationID = Medication.Id
WHERE PatientMedication.PatientID = #patientID
) AS i
) AS j )
END
By the way, unrelated to your error, but the query can be simplified:
CREATE FUNCTION [dbo].[getPatientMorphineEquivalentDose]
(
#patientID int
)
RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
RETURN (SELECT sum(pm.Mg * m.[Morphine Equivalent (mg)])
FROM dbo.PatientMedication pm
INNER JOIN dbo.Medication m
ON pm.MedicationID = m.Id
WHERE pm.PatientID = #patientID)
END

How to parse a VARCHAR passed to a stored procedure in SQL Server?

I have two tables tbl_Products and tbl_Brands, both are joined on BrandId.
I have a stored procedure which should return all the products belong to the brand ids passed to it as parameter.
My code is as follows.
create proc Sp_ReturnPrdoucts
#BrandIds varchar(500) = '6,7,8'
AS
BEGIN
SELECT *
FROM tbl_Products as p
JOIN tbl_Brands b ON p.ProductBrandId = b.BrandId
WHERE b.BrandId IN (#BrandIds)
END
But this is giving error as BrandId is INT and #BrandIds is VARCHAR
When I hard code it this way as follows it works fine and returns the desired data from db ..
create proc Sp_ReturnPrdoucts
#BrandIds varchar(500) = '6,7,8'
AS
BEGIN
SELECT *
FROM tbl_Products AS p
JOIN tbl_Brands b ON p.ProductBrandId = b.BrandId
WHERE b.BrandId IN (6,7,8)
END
Any help :)
If possible, don't use varchar for this kind of things, use a table valued parameter instead.
To use a tabled value parameter you should first declare a user defined table type:
CREATE TYPE IntList As Table
(
IntValue int
)
Then change your stored procedure to accept this variable instead of the nvarchar:
create proc Sp_ReturnPrdoucts
#BrandIds dbo.IntList readonly -- Note: readonly is a must!
AS
BEGIN
SELECT *
FROM tbl_Products as p
join tbl_Brands b on p.ProductBrandId=b.BrandId
join #BrandIds ON(b.BrandId = IntValue)
END
The problem is that the IN() operator expects a list of variables separated by commas, while you provide a single variable that it's value is a comma separated string.
If you can't use a table valued parameter, you can use a string spliting function in sql to convert the value of the varchar to a table of ints. there are many splitters out there, I would recommend reading this article before picking one.
Another alternative is to use 'indirection' (as I've always called it)
You can then do..
create proc Sp_ReturnPrdoucts
#BrandIds varchar(500) = '6,7,8'
AS
BEGIN
if (isnumeric(replace(#BrandIds,',',''))=1)
begin
exec('SELECT * FROM tbl_Products as p join tbl_Brands b on p.ProductBrandId=b.BrandId WHERE b.BrandId IN ('+#BrandIds+')')
end
END
This way the select statement is built as a string, then executed.
I've now added validation to ensure that the string being passed in is purely numeric (after removing all the commas)

UDF that accepts one parameter and returns a table

Question: Create a UDF that accepts the State Province and returns the associated Sales Tax Rate, StateProvinceCode and CountryRegionCode (Database is AdventureWorks2012)
Name took the place of State Province because there is no column
called 'State Province', and 'Name' contains the needed information.
This is what I did in the code below.
Is there another way to run this?
In this form Ambiguous column name keeps showing up.
CREATE FUNCTION fx_TxSpCr (#Name Nvarchar(50),
#TaxRate Smallmoney,
#StateProvinceCode Nchar(3),
#CountryRegionCode Nvarchar(3)
)
RETURNS TABLE
AS
RETURN (
SELECT Name,TaxRate,StateProvinceCode,CountryRegionCode
FROM Sales.SalesTaxRate s
JOIN Person.StateProvince t
ON s.StateProvinceID=t.StateProvinceID
JOIN Sales.SalesTerritory u
ON t.TerritoryID=u.TerritoryID
)
SELECT s.TaxRate,t.StateProvinceCode,u.CountryRegionCode
FROM fx_TxSpCr
GROUP BY Name
Thank you both for the contribution. This worked:
CREATE FUNCTION fx_TaxStCtry(#StateProvince nvarchar(50))
RETURNS #TaxStCtry TABLE
(TaxRate SmallMoney not null,
StateProvinceCode Nchar(3) not null ,
CountryRegionCode Nvarchar(3) not null)
AS
BEGIN INSERT #TaxStCtry
SELECT tr.TaxRat,sp.StateProvinceCode,sp.CountryRegionCode
FROM Person.StateProvince sp
JOIN Sales.SalesTaxRate tr
ON tr.stateprovinceid = sp.stateprovinceid
WHERE sp.Name=#StateProvince
RETURN
END
The ambiguity is coming from the lack of aliases in the select statement from your function. It seems like you are trying to use the aliases in the execution of the function, but they actually need to be contained within the function itself. (For example, all three tables have a "name" column.)
After correcting that issue you will find that you actually have not used any of the parameters you have set up for your function. Which does not have to be the full return set of the select statement contained within.
The correct path here is to start with a select statement that returns what you're needing and then turn that into a function. If the data is not being returned at a base level, then you're not ready to start creating a function.

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.