how to return a cell into a variable in sql functions - sql

I want to define a scaler function which in that I'm going to return the result into a variable but I do not know how to do this.
CREATE FUNCTION dbo.Funname ( #param int )
RETURNS INT
AS
declare #returnvar int
select #returnvar = select colname from tablename where someconditions = something
return(#returnvar)
I want to make a function something like the top. I mean the result of the select statement which is:
select colname from tablename where someconditions = something
Is only a single cell and we are sure about it. I want to store it into a variable and return it from the function. How can I implement this thing?

I should probably mention that scalar UDFs do come with a considerable health warning and can cause performance issues depending upon how you use them.
Here's an example though.
CREATE FUNCTION dbo.Funname ( #param INT )
RETURNS INT
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
RETURN (SELECT number FROM master.dbo.spt_values WHERE number < #param)
END
In the above example I didn't use a variable as it is redundant. The version with variable is
BEGIN
DECLARE #Result int
SET #Result = (SELECT number FROM master.dbo.spt_values WHERE number < #param)
RETURN #Result
END
For both of the above you would need to be sure the Query returned at most one row to avoid an error at runtime. For example
select dbo.Funname(-1) Returns -32768
select dbo.Funname(0) Returns error "Subquery returned more than 1 value."
An alternative syntax would be
BEGIN
DECLARE #Result int
SELECT #Result = number FROM master.dbo.spt_values WHERE number < #param
RETURN #Result
END
This would no longer raise the error if the subquery returned more than one value but you would just end up with an arbitrary result with no warning - which is worse.
Following Comments I think this is what you need
CREATE FUNCTION dbo.getcustgrade(#custid CHAR(200))
RETURNS INT
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
RETURN
( SELECT [cust grade]
FROM ( SELECT customerid,
DENSE_RANK() OVER (ORDER BY COUNT(*) DESC) AS [cust grade]
FROM Orders
GROUP BY CustomerID
)
d
WHERE customerid = #custid
)
END

Related

Passing a nullable local variable to a where condition

I have the following SQL function:
CREATE or ALTER FUNCTION [dbo].[GET_BOOK_CODE_BY_AUTHOR_ID]
(
#AUTHOR_ID int
)
RETURNS varchar(50)
AS
BEGIN
DECLARE
#MOT_N_ID int,
#SCO_N_ID int,
#BOOK_CODE varchar(50);
select
#MOT_N_ID = AUT.MOT_N_ID,
#SCO_N_ID = AUT.SCO_N_ID
from AUTHOR AUT
where AUT.AUT_ID = #AUTHOR_ID
SELECT
#BOOK_CODE = (
select BOO.BOO_CH_CODE
from BOOK BOO
where
and BOO.MOT_N_ID = #MOT_N_ID
and BOO.SCO_N_ID = #SCO_N_ID
)
RETURN #BOOK_CODE
END;
GO
The variable #SCO_N_ID can be a null value however and when it returns a null value SQL interprets the condition as:
BOO.SCO_N_ID = null
It is not interpreting it as:
BOO.SCO_N_ID is null
Any idea of how to do this?
I would do this check
"the variable is null or the field is equal to the variable"
and (#SCO_N_ID is null or BOO.SCO_N_ID = #SCO_N_ID)
How about something like this:
CREATE or ALTER FUNCTION [dbo].[GET_BOOK_CODE_BY_AUTHOR_ID]
(
#AUTHOR_ID int
)
RETURNS varchar(50)
AS
BEGIN
DECLARE
#BOOK_CODE varchar(50);
SELECT TOP 1 #BOOK_CODE = VEN_CH_CODE
FROM BOOK BOO
INNER JOIN AUTHOR AUT
ON AUT.MOT_N_ID = BOO.MOT_N_ID
WHERE
BOO.SCO_N_ID = AUT.SCO_N_ID OR
(BOO.SCO_N_ID IS NULL AND AUT.SCO_N_ID IS NULL)
ORDER BY BOO.SCO_N_ID, BOO.MOT_N_ID
RETURN #BOOK_CODE
END;
Note I have joined the tables to make one query, also note I have put a TOP 1 because it might be possible for the query to return more than one result set. If you use TOP 1 then you should have an ORDER BY clause to make sure you return the result you want
There are a few important points to note here:
Firstly, to compare while taking nulls into account, instead of a = b you can use this syntax exists (select a intersect select b). This will compile down to an IS comparison, as shown here, and is very efficient
You can dispense with all the variables and just do a simple join
User-defined scalar functions are very slow. You will get much better performance from an inline Table-Valued Function
CREATE or ALTER FUNCTION [dbo].[GET_BOOK_CODE_BY_AUTHOR_ID]
(
#AUTHOR_ID int
)
RETURNS TABLE
AS RETURN (
select VEN.VEN_CH_CODE
from BOOK BOO
join AUTHOR AUT on BOO.MOT_N_ID = AUT.MOT_N_ID
and exists (select BOO.SCO_N_ID
intersect
select AUT.SCO_N_ID)
where AUT.AUT_ID = #AUTHOR_ID
);
GO
You can use it like this
SELECT *
FROM GET_BOOK_CODE_BY_AUTHOR_ID(101) b;
Or like this
SELECT *
FROM OtherTable t
CROSS APPLY GET_BOOK_CODE_BY_AUTHOR_ID(t.authorId) b;
Or this
SELECT *,
(SELECT * FROM GET_BOOK_CODE_BY_AUTHOR_ID(t.authorId))
FROM OtherTable t;
I like to set both sides to an unavailable code, I will use -99 in this case.
ISNULL(BOO.SCO_N_ID,-99) = ISNULL(#SCO_N_ID,-99)

Error Number:141,State:1,Class:15 A SELECT statement that assigns a value to a variable must not be combined with data-retrieval operations

I am trying to write a function to get count of appointments with matching doctorId, here is function :
CREATE FUNCTION dbo.GetSumOfAppointments (#doctorId int)
RETURNS int
AS
BEGIN
DECLARE #count int;
SET #count = (SELECT
COUNT(*)
FROM dbo.Appointments
WHERE DoctorId = #doctorId);
RETURN ISNULL(#count, 0)
END
but its giving me error :
Error Number:141,State:1,Class:15 A SELECT statement that assigns a
value to a variable must not be combined with data-retrieval
operations.
what exactly is the problem with above?
CREATE FUNCTION dbo.GetSumOfAppointments (#doctorId int)
RETURNS int
AS
BEGIN
DECLARE #count int;
select #count = COUNT(*)
FROM dbo.Appointments
WHERE DoctorId = #doctorId;
RETURN ISNULL(#count, 0)
END
No need to declare a variable and applying Isnull function since count(*) always returns number of records in that table suppose if the table is empty then you would get zero. Try like this make it simple.
CREATE FUNCTION dbo.GetSumOfAppointments (#doctorId INT)
RETURNS INT
AS
BEGIN
RETURN (
SELECT COUNT(*)
FROM dbo.Appointments
WHERE DoctorId = #doctorId
);
END

SQL Server Scalar Function with "IN" Getting Subquery Returned more than 1 Error

Here's the query which works well:
select MIN(dbo.GetDiscountedPrice(P.AutoID))
FROM Product P
where P.AutoID in (2910,2912,2820)
It returns the cheapest price among 2910,2912 and 2820 (AutoID is the primary key)
BUT this query returns an error:
select MIN(dbo.GetDiscountedPrice(P.AutoID))
FROM Product P
where P.AutoID in (SELECT AutoID FROM Product WHERE Category=2)
Error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
This query actually returns 2910,2912,2820
SELECT AutoID FROM Product WHERE Category=2
Why is that so and how can I achieve the purpose with by getting products based on category?
Function GetDiscountedPrice:
It's just a very simple function which:
Gets the price
Gets the percentage
Returns the discounted price after minus off the percentage
Note that percentage was taken from another table called ProductDiscount.
CREATE FUNCTION [dbo].[GetDiscountedPrice]
(
#ProductAutoID bigint
)
RETURNS decimal(10,4)
AS
BEGIN
DECLARE #Result decimal(10,4)
Declare #Price float = (select Price
from Product
where AutoID = #ProductAutoID)
Declare #DP float = ISNULL
(
(select DiscountPercentage
from ProductDiscount
where Product_AutoID = #ProductAutoID), 0)
set #Result = #Price - (#Price * 0.01 * #DP)
RETURN #Result
END
If statement
where P.AutoID in (SELECT AutoID FROM Product WHERE Category=2)
fails but
where P.AutoID in (2910,2912,2820)
works, then the problem is definitely in function itself, because first statement can not produce mentioned error. I.e. first statement returns different set then second statement and data produces that error probably here:
Declare #Price float = (select Price
from Product
where AutoID = #ProductAutoID)
and here:
(select DiscountPercentage
from ProductDiscount
where Product_AutoID = #ProductAutoID)
Those subqueries are returning more then 1 row, so the error occures.
try this code (i did not check it)-
DECLARE #tbl TABLE (id int IDENTITY(1,1),AutoID int)
INSERT INTO #tbl
SELECT AutoID FROM Product WHERE Category=2
DECLARE #x int=1
DECLARE #y int=1
WHILE #x <= (SELECT count (*) from #tbl)
BEGIN
set #y=(select AutoID FROM #tbl WHERE id=#x)
select MIN(dbo.GetDiscountedPrice(#y))
FROM Product P
SET #x=#x+1
END
Based on everything you're saying, this should work:
select MIN(AutoID)
FROM Product
where Category=2
Q: does this work?
select GetDiscountedPrice(MIN(AutoID))
FROM Product
where Category=2
Q: If not, could you please post "GetDiscountedPrice"?
The problem lies on the function, because it exists more than 1 DiscountPercentage for a single product. Adding "DISTINCT" in the function when selecting the DiscountPercentage eliminates the duplicate data.
Declare #DP float = ISNULL
(
(select DISTINCT DiscountPercentage
from ProductDiscount
where Product_AutoID = #ProductAutoID), 0)

Error using Common Table Expression in SQL User Defined Function

CREATE FUNCTION [dbo].[udfGetNextEntityID]
()
RETURNS INT
AS
BEGIN
;WITH allIDs AS
(
SELECT entity_id FROM Entity
UNION SELECT entity_id FROM Reserved_Entity
)
RETURN (SELECT (MAX(entity_id) FROM allIDs )
END
GO
SQL isn't my strong point, but I can't work out what I'm doing wrong here. I want the function to return the largest entity_id from a union of 2 tables. Running the script gives the error:
Incorrect syntax near the keyword 'RETURN'.
I looked to see if there was some restriction on using CTEs in functions but couldn't find anything relevant. How do I correct this?
CREATE FUNCTION [dbo].[udfGetNextEntityID]()
RETURNS INT
AS
BEGIN
DECLARE #result INT;
WITH allIDs AS
(
SELECT entity_id FROM Entity
UNION SELECT entity_id FROM Reserved_Entity
)
SELECT #result = MAX(entity_id) FROM allIDs;
RETURN #result;
END
GO
While you can do it, why do you need a CTE here?
RETURN
(
SELECT MAX(entity_id) FROM
(
SELECT entity_id FROM dbo.Entity
UNION ALL
SELECT entity_id FROM dbo.Reserved_Entity
) AS allIDs
);
Also there is no reason to use UNION instead of UNION ALL since this will almost always introduce an expensive distinct sort operation. And please always use the schema prefix when creating / referencing any object.
You can not return the way your are doing from the function.
Make use of a local variable and return the same.
CREATE FUNCTION [dbo].[udfGetNextEntityID]()
RETURNS INT
AS
BEGIN
DECLARE #MaxEntityId INT;
WITH allIDs AS
(
SELECT entity_id FROM Entity
UNION SELECT entity_id FROM Reserved_Entity
)
SELECT #MaxEntityId = MAX(entity_id) FROM allIDs;
RETURN #MaxEntityId ;
END
GO
create function tvfFormatstring (#string varchar(100))
returns #fn_table table
(id int identity(1,1),
item int)
as
begin
insert into #fn_table(item)
declare #result int
set #string = #string+'-'
;with cte (start,number)
as
(
select 1 as start , CHARINDEX('-',#string,1) as number
union all
select number+1 as start , CHARINDEX('-',#string,number+1) as number from cte
where number <= LEN(#string)
)
select #result = SUBSTRING(#string,start,number-start) from cte ;
return #result;
end
select * from tvfFormatstring ('12321-13542-15634')

SQL UDF Group By Parameter Issue

I'm having some issues with a group by clause in SQL. I have the following basic function:
CREATE FUNCTION dbo.fn_GetWinsYear (#Year int)
RETURNS int
AS
BEGIN
declare #W int
select #W = count(1)
from tblGames
where WinLossForfeit = 'W' and datepart(yyyy,Date) = #Year
return #W
END
I'm trying to run the following basic query:
select dbo.fn_GetWinsYear(datepart(yyyy,date))
from tblGames
group by datepart(yyyy,date)
However, I'm encountering the following error message: Column 'tblGames.Date' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Any ideas why this is occurring? FYI, I know I can remove the function and combine into one call but I'd like to keep the function in place if possible.
I think you should be calling your function like this.
select dbo.fn_GetWinsYear(datepart(yyyy,getdate()))
OR
select dbo.fn_GetWinsYear('2010')
Essentially you are just passing a year to your function and the function is returning the number of wins for that year.
If you don't know the year, your function could look something like this...
CREATE FUNCTION dbo.fn_GetWinsYear ()
RETURNS #tblResults TABLE
( W INT, Y INT )
AS
BEGIN
INSERT #tblResults
SELECT count(1), datepart(yyyy,[Date])
FROM tblGames
WHERE WinLossForfeit = 'W'
GROUP BY datepart(yyyy,[Date])
RETURN
END
SELECT * FROM dbo.fn_GetWinsYear()