A SQL function with a pure select result - sql

At first I should say I am newbie in SQL functions or store procedures, I want to know how can I create a sql function or store procedures with some filters and sorting but at least it return just a pure select without showing filters and sorts
I mean I want to create something like this function :
function {
SELECT * FROM Customers
Where (ID = #p1)
ORDER BY #P2 DESC;
}
but it returns just this select commend to me :
SELECT * FROM Customers
but what it returns filtered and sorted.
how can I do this? I should do this with functions or store procedures?

You could use a Stored Procedure or a table-valued function:
CREATE PROCEDURE MyProc #P1 int, #P2 int as
BEGIN
SELECT FirstName, LastName, LastOrderNum, LastPurchaseDate
FROM Customers
WHERE ID = #P1
ORDER BY CASE WHEN #P2 = 0 THEN LastName ELSE LastPurchaseDate END DESC;
END
usage: exec MyProc 10, 1;
Or:
CREATE FUNCTION my_schema.MyTableFun (#P1 int, #P2 int)
RETURNS TABLE
AS
RETURN
SELECT TOP 100 PERCENT FirstName, LastName, LastOrderNum, LastPurchaseDate
FROM Customers
WHERE ID = #P1
ORDER BY CASE WHEN #P2 = 0 THEN LastName ELSE LastPurchaseDate END DESC;
usage:
SELECT * FROM my_schema.MyTableFun(10, 1);

Related

How can I write SQL select statement inside scalar type user-defined function?

I am trying to create a function in SQL Server using the following, but I think I am missing something in either in syntax or query
CREATE FUNCTION DEMO.Get_Rate_For_Absence
(#company_id_ VARCHAR,
#emp_no_ VARCHAR,
#account_date_ DATE)
RETURN DECIMAL(10, 2) AS
BEGIN
DECLARE #RATE_ DECIMAL(10, 2)
SET #RATE_ = SELECT rate
FROM DEMO.Employee
WHERE COMPANY_ID = '#company_id_ '
AND Emp_no = '#emp_no_ '
AND ORG_CODE = '#wage_code_'
AND ACCOUNT_DATE = '#account_date_'
RETURN #RATE
END
The SQL statement that I am trying to write inside function code block is:
SELECT DISTINCT rate
FROM DEMO.Employee
WHERE Company_ID = #company_id_
AND EMP_NO = #emp_no_
AND ACCOUNT_DATE = #account_date_
Something like:
CREATE OR ALTER FUNCTION DEMO.Get_Rate_For_Absence
(#company_id VARCHAR(200),
#emp_no VARCHAR(200),
#account_date DATE)
RETURNS DECIMAL(10, 2) AS
BEGIN
DECLARE #RATE DECIMAL(10, 2)
SET #RATE = (
SELECT rate
FROM DEMO.Employee
WHERE COMPANY_ID = #company_id
AND Emp_no = #emp_no
AND ACCOUNT_DATE = #account_date
)
RETURN #RATE
END
Perhaps you actually want to return a whole resultset rather than just a single value.
Then you should use an inline Table Valued Function (of the form RETURNS TABLE AS RETURN SELECT ...) which in any case performs much better than a scalar function.
Variables don't go in quotes so you just do COMPANY_ID = #company_id_.
Always declare varchar with a length.
CREATE OR ALTER FUNCTION DEMO.Get_Rate_For_Absence (
#company_id_ VARCHAR(100),
#emp_no_ VARCHAR(100),
#wage_code_ VARCAHR(100),
#account_date_ DATE
)
RETURNS TABLE AS RETURN
SELECT e.rate
FROM DEMO.Employee e
WHERE e.COMPANY_ID = #company_id_
AND e.Emp_no = #emp_no_
AND e.ORG_CODE = #wage_code_
AND e.ACCOUNT_DATE = #account_date_;
You use it slightly differently than scalar functions, as it goes in the FROM part
SELECT r.rate
FROM DEMO.Get_Rate_For_Absence('a', 'b', 'c', GETDATE()) r;
Or
SELECT r.rate
FROM SomeTable t
CROSS APPLY DEMO.Get_Rate_For_Absence(t.a, t.b, t.c, t.date) r;

Create dynamic SQL query for search

I need to create a SQL query to return data from a table and use condition in where but I where not have not have fixed condition, may be condition have 2 or 3 parameters, I want to avoid the IF #PARAMETER >0. I want to use a better and cleaner way to solve it.
This is my query :
CREATE PROCEDURE GetAllUsers(#p1 , #p2 , #p3 , #p4)
AS
BEGIN
SELECT
firstName,
lastName,
email
FROM
[USERS]
WHERE
id = #p4 AND firstName = #p2
END
How can I solve this issue?
You can use COALESCE to make a parameter equal to the column it is being compared against when it is NULL, so that the test passes if you pass NULL for the parameter value. For example:
SELECT
firstName,
lastName,
email
FROM [USERS]
WHERE id=COALESCE(#p4,id) AND firstName=COALESCE(#p2,firstName)
So when calling your procedure and wanting to fetch all John regardless of id you would use
GetAllUsers('somevalue' , 'John' , 'someothervalue' , NULL)
You can pass NULL to parameter which are not required and write your query like following.
SELECT firstName, lastName, email
FROM [USERS]
WHERE (
#p4 IS NULL
OR id = #p4
)
AND (
#p2 IS NULL
OR firstName = #p2
)
Similarly you can build your query by adding other parameter.
NOTE: Syntax of your CREATE PROCEDURE is not correct, you need to mention the datatype of the parameters.
You can also use the default values to your parameters. Your final SP should look like following
CREATE PROCEDURE GetAllUsers (
#p1 INT = NULL
,#p2 VARCHAR(100) = NULL
,#p3 VARCHAR(100) = NULL
,#p4 VARCHAR(100) = NULL
)
AS
BEGIN
SELECT firstName
,lastName
,email
FROM [USERS]
WHERE (
#p4 IS NULL
OR id = #p4
)
AND (
#p2 IS NULL
OR firstName = #p2
)
--Add remaining conditions
END

Different ways a stored procedure returns data

I have the following stored procedure:
CREATE PROCEDURE spGetSalaryInfo
#AverageSalary INT OUT
AS
BEGIN
SELECT MAX(1) MaxSalary;
SELECT #AverageSalary = AVG(1);
RETURN (SELECT COUNT(1) Total);
END
When I execute the query, I get 3 result set. One from SELECT statement, one from OUTPUT parameters and the last one from RETURN statement.
DECLARE #MaximumSalary INT, #AverageSalary INT, #TotalEmployee int;
EXEC #TotalEmployee = spGetSalaryInfo #AverageSalary OUT
SELECT #AverageSalary AS AVGERAGE
SELECT #TotalEmployee AS TotalEmployee
Output:
MaxSalary AVGERAGE TotalEmployee
----------- ----------- -------------
1 1 1
As you can see, I can return data by selecting SELECT query or OUTPUT parameter inside a stored procedure as well as by return statement.
I wonder, at what scenario we can use this type of stored procedure. I am confused a little.
This is a bit of an odd way to return three variables from a stored procedure, the typical case would choose to either return all three as output parameters, or all three as columns of a single-row result.
I wonder, at what scenario we can use this type of stored procedure. I am confused a little.
In most cases a select statement will return a set of data (i.e one with many rows for a set of columns) but in certain cases (of which this looks like one) you just have some discreet values and not a set of values.
Why didn't you try like this,
CREATE PROCEDURE spGetSalaryInfo
AS
BEGIN
DECLARE
#TotalEmployee INT
,#MaxSalary INT
SELECT #MaxSalary = MAX(1)
SELECT #AverageSalary = AVG(1)
SELECT #TotalEmployee = COUNT(1)
SELECT
#MaxSalary AS MaxSalary
,#AverageSalary AS AVGERAGE
,#TotalEmployee AS TotalEmployee
END

How can I call and get back the results of an SP from another Stored Procedure

I have this stored procedure:
CREATE PROCEDURE [dbo].[sp_get_correct_responses]
#QuestionUId UNIQUEIDENTIFIER
AS
BEGIN
...
-- This is the last part of the SP. I need to use the output
-- value of #AnswerGridCorrect in the calling SP
SELECT #AnswerGridCorrect = Correct
FROM Concatenated
WHERE RowNumber = ColumnCount
END
How can I call the stored procedure from another stored procedure, pass it the #QuestionUId parameter and put the returned variable #AnswerGridCorrect into a variable declared in the calling procedure?
Update: Here's the proposed answer:
CREATE PROCEDURE [dbo].[sp_get_correct_responses]
#QuestionUId UNIQUEIDENTIFIER,
#output VARCHAR(20) output
AS
BEGIN
select #QuestionUId
DECLARE #AnswerGridCorrect VARCHAR(20)
DECLARE #QuestionId int;
SELECT #QuestionId = QuestionId
FROM dbo.Question
Where QuestionUId = #QuestionUId;
Select #questionId;
WITH Partitioned AS (
SELECT ROW_NUMBER() OVER (PARTITION BY QuestionId ORDER BY AnswerId ASC) AS RowNumber,
COUNT(1) OVER (PARTITION BY QuestionId) AS ColumnCount,
CONVERT(VARCHAR(MAX), Correct) AS Correct
FROM dbo.Answer
WHERE [QuestionId] = #QuestionId
),
Concatenated AS (
SELECT RowNumber, ColumnCount, Correct FROM Partitioned WHERE RowNumber = 1
UNION ALL
SELECT P.RowNumber,
P.ColumnCount,
C.Correct + P.Correct AS Correct
FROM Partitioned P
INNER JOIN Concatenated C
ON P.RowNumber = C.RowNumber + 1
)
SET #output = (SELECT Correct
FROM Concatenated
WHERE RowNumber = ColumnCount)
RETURN
END
You could have a temp table in the other stored procedure and populate it with the results of this one:
INSERT INTO #table
Exec sp_get_correct_responses #QuestionUId
The other way would be to modify sp_get_correct_responses to produce an output as you are expecting only one value.
CREATE PROCEDURE [dbo].[sp_get_correct_responses]
#QuestionUId UNIQUEIDENTIFIER,
#output VARCHAR(20) output
AS
BEGIN
...
-- This is the last part of the SP. I need to use the output
-- value of #AnswerGridCorrect in the calling SP
SELECT #output = Correct
FROM Concatenated
WHERE RowNumber = ColumnCount
RETURN
END
And in your other SP:
DECLARE #output VARCHAR(20)
EXEC sp_get_correct_responses
#QuestionUId,
#output output
SELECT #output
You can make one table variable in parent SP and insert result of child SP in that like below :
DECLARE #TempTable TABLE(AnswerGridCorrect INT)
INSERT INTO #TempTable
EXEC [dbo].[sp_get_correct_responses] #QuestionUId

SQL Table Valued Function in Select Statement

SQL is not my best thing but I have been trying to optimize this stored procedure. It had multiple scalar-valued functions that I tried to change to table-valued functions because I read in many places that it's a more efficient way of doing it. And now I have them made but not real sure how to implement or if I maybe just didn't create them correctly.
This is the function I'm calling.
Alter FUNCTION [IsNotSenateActivityTableValue]
(
#ActivityCode int,
#BillId int,
#TextToDisplay varchar(max)
)
returns #T table(result varchar(max))
as
begin
DECLARE #result varchar(max);
declare #countcodes int;
declare #ishousebill int;
select #ishousebill = count(billid)
from BillMaster
where BillID = #BillID and Chamber = 'H'
If (#ishousebill = 0)
begin
SELECT #countcodes = count([ActivityCode])
FROM [HouseCoreData].[dbo].[ActivityCode]
where ActivityDescription not like '%(H)%' and ActivityType = 'S'
and [ActivityCode] = #ActivityCode
if (#countcodes = 0)
begin
set #result = 'test'
end
else
begin
set #result = 'test2'
end
end
else
begin
set #result = #TextToDisplay
end
RETURN
END
And this is how I was trying to call them like this. I would prefer just being able to put them in the top but really anything that works would be good.
SELECT distinct
ActionDates.result as ActionDate
,ActivityDescriptions.result as ActivityDescription
FROM BillWebReporting.vwBillDetailWithSubjectIndex as vw
left outer join [BillWebReporting].[HasHouseSummary] as HasSummary on vw.BillID = HasSummary.BillID
outer APPLY dbo.IsNotSenateActivityDateTableValue(ActivityCode,vw.BillID,[ActionDate]) ActionDates
OUTER APPLY dbo.IsNotSenateActivityTableValue(ActivityCode,vw.BillID,[ActivityDescription]) as ActivityDescriptions
Getting a count just to see if at least one row exists is very expensive. You should use EXISTS instead, which can potentially short circuit without materializing the entire count.
Here is a more efficient way using an inline table-valued function instead of a multi-statement table-valued function.
ALTER FUNCTION dbo.[IsNotSenateActivityTableValue] -- always use schema prefix!
(
#ActivityCode int,
#BillId int,
#TextToDisplay varchar(max)
)
RETURNS TABLE
AS
RETURN (SELECT result = CASE WHEN EXISTS
(SELECT 1 FROM dbo.BillMaster
WHERE BillID = #BillID AND Chamber = 'H'
) THEN #TextToDisplay ELSE CASE WHEN EXISTS
(SELECT 1 FROM [HouseCoreData].[dbo].[ActivityCode]
where ActivityDescription not like '%(H)%'
and ActivityType = 'S'
and [ActivityCode] = #ActivityCode
) THEN 'test2' ELSE 'test' END
END);
GO
Of course it could also just be a scalar UDF...
ALTER FUNCTION dbo.[IsNotSenateActivityScalar] -- always use schema prefix!
(
#ActivityCode int,
#BillId int,
#TextToDisplay varchar(max)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #result VARCHAR(MAX);
SELECT #result = CASE WHEN EXISTS
(SELECT 1 FROM dbo.BillMaster
WHERE BillID = #BillID AND Chamber = 'H'
) THEN #TextToDisplay ELSE CASE WHEN EXISTS
(SELECT 1 FROM [HouseCoreData].[dbo].[ActivityCode]
where ActivityDescription not like '%(H)%'
and ActivityType = 'S'
and [ActivityCode] = #ActivityCode
) THEN 'test2' ELSE 'test' END
END;
RETURN (#result);
END
GO
Table-valued functions return a table, in which, like any other table, rows have to be inserted.
Instead of doing set #result = ....., do:
INSERT INTO #T (result) VALUES ( ..... )
EDIT: As a side note, I don't really understand the reason for this function to be table-valued. You are essentially returning one value.
First of all UDFs generally are very non-performant. I am not sure about MySQL, but in Sql Server a UDF is recompiled every time (FOR EACH ROW OF OUTPUT) it is executed, except for what are called inline UDFs, which only have a single select statement, which is folded into the SQL of the outer query it is included in... and so is only compiled once.
MySQL does have inline table-valued functions, use it instead... in SQL Server, the syntax would be:
CREATE FUNCTION IsNotSenateActivityTableValue
(
#ActivityCode int,
#BillId int,
#TextToDisplay varchar(max)
)
RETURNS TABLE
AS
RETURN
(
Select case
When y.bilCnt + z.actCnt = 0 Then 'test'
when y.bilCnt = 0 then 'test2'
else #TextToDisplay end result
From (Select Count(billId) bilCnt
From BillMaster
Where BillID = #BillID
And Chamber = 'H') y
Full Join
(Select count([ActivityCode]) actCnt
From [HouseCoreData].[dbo].[ActivityCode]
Where ActivityDescription not like '%(H)%'
And ActivityType = 'S'
And [ActivityCode] = #ActivityCode) z
)
GO