SQL Create View and using it in Function - sql

I have the following function and I need to take out the SELECT part and create a separate view.
CREATE FUNCTION dbo.dbf_get_penalty_points
( #pn_seq_party_id NUMERIC(18),
#pv_penalty_points_code CHAR(1) = 'Y') -- Use 'N' for total points, otherwise will return Current Penalty Points
RETURNS NUMERIC(18,0)
AS
BEGIN
DECLARE #n_penalty_points NUMERIC(18),
#d_latest_points_date DATETIME
SELECT #d_latest_points_date = dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate()))
SELECT #n_penalty_points = IsNull(Sum(penalty_points_amount),0)
FROM dbo.ar_penalty_point WITH(NOLOCK)
WHERE seq_party_id = #pn_seq_party_id
AND 1 = CASE
WHEN #pv_penalty_points_code = 'N' THEN 1
WHEN #pv_penalty_points_code = 'Y' AND added_date >= #d_latest_points_date AND reset_date IS NULL THEN 1
ELSE 0
END
RETURN #n_penalty_points
END
GO
SET QUOTED_IDENTIFIER OFF
GO
GRANT EXECUTE ON dbo.dbf_get_penalty_points TO standard
GO
I have tried and got this,
SELECT SUM(CASE WHEN added_date >=dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate()))
AND reset_date IS NULL THEN 1
ELSE 0) current_points,
IsNull(Sum(penalty_points_amount),0) total_points,
seq_party_id
FROM dbo.ar_penalty_point WITH(NOLOCK)
GROUP BY seq_party_id
Now I need to get rid of
dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate()))
From the SELECT part of the query. I am struck is there a better way to write my view ?
EDIT
The objective is to create a view that returns total_points and current_points. For better understanding refer the CREATE part following
CREATE FUNCTION dbo.dbf_get_penalty_points
( #pn_seq_party_id NUMERIC(18),
#pv_penalty_points_code CHAR(1) = 'Y') -- Use 'N' for total points, otherwise will return Current Penalty Points
Refer to -- Use 'N' for total points, otherwise will return Current Penalty Points in the comments

This is what I came up with
SELECT SUM(CASE WHEN (t.added_date >= t.target_date
AND t.reset_date IS NULL) THEN 1
ELSE 0 END) current_points,
IsNull(Sum(t.penalty_points_amount),0) total_points,
t.seq_party_id
FROM (
SELECT dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate())) as target_date,
u.reset_date, u.penalty_points_amount,u.seq_party_id,u.added_date FROM
dbo.ar_penalty_point as u ) as t GROUP BY t.seq_party_id

Related

Add column name to a variable and use it in later calculation in WHERE clause

I have a problem. I need to determine the name of the column under which the calculations will continue. So I wrote a select:
DECLARE #column VARCHAR(MAX)
DECLARE #ColumnA VARCHAR(MAX)
DECLARE #ColumnB VARCHAR(MAX)
SET #ColumnA = 'RegistrationDate'
SET #ColumnB = 'EntryDate'
SET #column = CASE
WHEN CONVERT(DATE,GETDATE()) NOT IN (
'2021-08-04','2021-08-05','2021-08-06','2021-08-07','2021-08-08','2021-08-09','2021-08-10','2021-09-07','2021-09-08','2021-09-09','2021-09-10','2021-09-11',
'2021-09-12','2021-09-13','2021-10-05','2021-10-06','2021-10-07','2021-10-08','2021-10-09','2021-10-10','2021-10-11','2021-11-09','2021-11-10','2021-11-11','2021-11-12','2021-11-13','2021-11-14','2021-11-15','2021-12-07',
'2021-12-08','2021-12-09','2021-12-10','2021-12-11','2021-12-12','2021-12-13'
) THEN
QUOTENAME(#Column)
ELSE
QUOTENAME(#ColumnB)
END
SELECT #column
which returns me [RegistrationDate] or [EntryDate] and stores this in variable #column. Now, when I know under which column should I calculate, I want to insert this variable #column in to my main select one of the WHERE clause:
DECLARE #column VARCHAR(MAX)
DECLARE #ColumnA VARCHAR(MAX)
DECLARE #ColumnB VARCHAR(MAX)
SET #ColumnA = 'RegistrationDate'
SET #ColumnB = 'EntryDate'
SET #column = CASE
WHEN CONVERT(DATE,GETDATE()) NOT IN (
'2021-08-04','2021-08-05','2021-08-06','2021-08-07','2021-08-08','2021-08-09','2021-08-10','2021-09-07','2021-09-08','2021-09-09','2021-09-10','2021-09-11',
'2021-09-12','2021-09-13','2021-10-05','2021-10-06','2021-10-07','2021-10-08','2021-10-09','2021-10-10','2021-10-11','2021-11-09','2021-11-10','2021-11-11','2021-11-12','2021-11-13','2021-11-14','2021-11-15','2021-12-07',
'2021-12-08','2021-12-09','2021-12-10','2021-12-11','2021-12-12','2021-12-13'
) THEN
QUOTENAME(#Column)
ELSE
QUOTENAME(#ColumnB)
END
SELECT
CASE WHEN final.Branch IS NULL THEN 'Total'
ELSE final.Branch
END AS 'Branch',
final.TR
FROM
(
SELECT
CASE
WHEN main.BRANCHNO = 1 THEN 'One'
WHEN main.BRANCHNO = 2 THEN 'Two'
WHEN main.BRANCHNO = 3 THEN 'Three'
WHEN main.BRANCHNO = 4 THEN 'Four'
WHEN main.BRANCHNO = 5 THEN 'Five'
WHEN main.BRANCHNO = 6 THEN 'Six'
END AS 'Branch',
COUNT(*) AS 'TR'
FROM
(
SELECT
*
FROM
[TABLE]
WHERE
Status = 100
AND
BRANCHNO IN (1,2,3,4,5,6)
AND
Type = 'TR'
AND
**#column** = CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) = 'Monday' THEN getdate()-3 ELSE getdate()-1 END
)
) AS main
GROUP BY
main.BRANCHNO WITH ROLLUP
) AS final
But when I execute query it returns me an error:
Msg 241, Level 16, State 1, Line 11 Conversion failed when converting
date and/or time from character string.
I imagined everything very simple: I put a column name into a variable, and then, that name placed at the beginning of the WHERE clause will be recognized as the column name and then *= CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) etc will do all work.
But that did not happen. Maybe someone knows why and maybe they know how to solve this task?
You can't use a variable to reference a column name. #column is just a piece of data, which just so happens to contain a column name as a string, but it's still just a string, not actually a reference to a column in a table.
Some options you have seem to be...
AND CASE #column WHEN 'RegistrationDate' THEN RegistrationDate
WHEN 'EntryDate' THEN EntryDate
END
=
CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) = 'Monday' THEN getdate()-3 ELSE getdate()-1 END)
Or, have two queries which only differ in the column being referenced...
IF (#column = 'RegistrationDate')
<query1>
ELSE IF (#column = 'EntryDate')
<query2>
Or "Dynamic SQL" where you build up a new string with your SQL code and execute that by call sp_executesql (assuming this is SQL Server, which it appears to be).
I recommend reading this : https://www.sommarskog.se/dyn-search.html
EDIT: A pure SQL alternative, assuming SQL Server
DECLARE #mode INT = CASE
WHEN CONVERT(DATE,GETDATE()) NOT IN (
'2021-08-04','2021-08-05','2021-08-06','2021-08-07','2021-08-08','2021-08-09','2021-08-10','2021-09-07','2021-09-08','2021-09-09','2021-09-10','2021-09-11',
'2021-09-12','2021-09-13','2021-10-05','2021-10-06','2021-10-07','2021-10-08','2021-10-09','2021-10-10','2021-10-11','2021-11-09','2021-11-10','2021-11-11','2021-11-12','2021-11-13','2021-11-14','2021-11-15','2021-12-07',
'2021-12-08','2021-12-09','2021-12-10','2021-12-11','2021-12-12','2021-12-13'
) THEN
0
ELSE
1
END;
DECLARE #filter_date DATE = CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) = 'Monday' THEN getdate()-3 ELSE getdate()-1 END;
WITH
source AS
(
SELECT
*
FROM
[TABLE]
WHERE
Status = 100
AND BRANCHNO IN (1,2,3,4,5,6)
AND Type = 'TR'
),
filtered_source AS
(
SELECT 0 AS mode, * FROM source WHERE RegistrationDate = #filter_date
UNION ALL
SELECT 1 AS mode, * FROM source WHERE EntryDate = #filter_date
)
SELECT
COALESCE(
CASE
WHEN BRANCHNO = 1 THEN 'One'
WHEN BRANCHNO = 2 THEN 'Two'
WHEN BRANCHNO = 3 THEN 'Three'
WHEN BRANCHNO = 4 THEN 'Four'
WHEN BRANCHNO = 5 THEN 'Five'
WHEN BRANCHNO = 6 THEN 'Six'
END,
'Total'
)
AS 'Branch',
COUNT(*) AS 'TR'
FROM
filtered_source
WHERE
mode = #mode
GROUP BY
GROUPING SETS (
(mode),
(mode, BRANCHNO)
);
By always including mode in the GROUPING SETS, the optimiser might be able to yield a better execution plan for the two scenarios.
Still read the link given above though, at the very least to understand why this is necessary, or perhaps why it doesn't quite manage to yield the best execution plan.

Why is SQL server inserting 0 values into my table instead of the correct values using function

Hope somebody can help me with this as I'm completely out of ideas as to why it's happening.
I am currently conducting some analysis on Premier League Match Results and as part of this, I have created a Multi-Statement Table UDF.
This function accepts a HomeTeam, AwayTeam and a MatchDate parameter, and then performs a count of each match result that was won, drawn or lost historically between the home and away team prior to the up match date specified.
This function works fine by manually calling it, and returns values such as
Home Away Draw
0 8 4
I wanted to add this information to every match result in my match table, so created a query to move the matches from a staging table, using OUTER APPLY with the function to insert these values.(I also OUTER APPLY another function prior to this which works fine.) and then insert into my MatchData table.
The query works if I just select the values, but if I INSERT INTO my MatchData table, the values are all populating as 0s.
I have tried numerous tests, which confirm that this also happens if I use a SELECT into, unless the table is temporary.
To add, there is no conversion of the values in question at any point, they remain as integers all the way through and the destination column is of type int also.
Hope somebody can give me some ideas on what to try next. Code is below. apologies for anything that isn't well written, as I have muddled with the code a lot up to now trying to get it to insert the right value
Thanks in advance!
Here's the stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [Load].[MoveToMatchData]
AS
BEGIN
INSERT INTO Football.MatchData.PremierLeague
SELECT *
FROM
(SELECT
[League], [MatchID], [Season],
[MatchDate], [HomeTeam], [AwayTeam],
[FTHomeGoals], [FTAwayGoals], [FTResult],
[HTHomeGoals], [HTAwayGoals], [HTResult],
[Referee], [HomeShots], [AwayShots],
[HomeShotsOnTarget], [AwayShotsOnTarget],
[HomeFouls], [AwayFouls], [HomeCorners], [AwayCorners],
[HomeYellows], [AwayYellows], [HomeReds], [AwayReds]
FROM
[Football].[Load].[Staging_MatchData] AS a
WHERE
League = 'E0') AS a
OUTER APPLY
(
SELECT * FROM Football.Load.CreateRelativeTable_Prem
(a.MatchDate, a.HomeTeam, a.AwayTeam, a.Season, A.League)
) as b
OUTER APPLY
Here's the UDF
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [Load].[GetH2HRecords]
(#HomeTeam varchar(50), #AwayTeam varchar(50), #MatchDate date)
RETURNS #H2H TABLE
(
Home int,
Away int,
Draw int
)
AS
BEGIN
DECLARE #FromDate date
SET #FromDate = DATEADD(yyyy,-10,#MatchDate)
INSERT INTO #H2H
SELECT
a.[Number of Matches] as HomeHTH, b.[Number of Matches] as AwayHTH, c.[Number of Matches] as DrawHTH
FROM
(
SELECT
COUNT(MatchID) as [Number of Matchesh]
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
AND FTResult = 'H'
) as a
OUTER APPLY
(
SELECT
COUNT(MatchID) as [Number of Matchesa]
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
AND FTResult = 'A'
) as b
OUTER APPLY
(
SELECT
COUNT(MatchID) as [Number of Matchesd]
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
AND FTResult = 'D'
) as c
RETURN
END
(
SELECT * FROM Football.Load.GetH2HRecords
(a.HomeTeam, a.AwayTeam, a.MatchDate)
) as c
END
This is only potentially an answer, but... why is your Function query so complex? This does the same thing in only one SELECT statement:
CREATE FUNCTION [Load].[GetH2HRecords]
(#HomeTeam varchar(50), #AwayTeam varchar(50), #MatchDate date)
RETURNS #H2H TABLE
(
Home int,
Away int,
Draw int
)
AS
BEGIN
DECLARE #FromDate date
SET #FromDate = DATEADD(yyyy,-10,#MatchDate)
INSERT INTO #H2H
SELECT
SUM(
CASE
WHEN FTResult = 'H'
Then 1
ELSE 0
END
) as HomeHTH,
SUM(
CASE
WHEN FTResult = 'A'
Then 1
ELSE 0
END
) as AwayHTH,
SUM(
CASE
WHEN FTResult = 'D'
Then 1
ELSE 0
END
) as DrawHTH
FROM MatchData.PremierLeague
WHERE HomeTeam = #HomeTeam
AND AwayTeam = #AwayTeam
AND MatchDate > #FromDate
RETURN
END

SQL How to count all remains for each date

I have the following SQL function
CREATE FUNCTION [dbo].[GetCardDepartRemains]
(
#CardId INT,
#DepartId INT,
#Date DATETIME = NULL,
#DocumentId INT = NULL
)
RETURNS INT
AS
BEGIN
DECLARE #Res INT
SELECT
#Res = ISNULL(SUM(CASE WHEN Operations.[Output] = 0 AND Operations.RecipientId = #DepartId THEN 1 ELSE -1 END), 0)
FROM dbo.Operations
WHERE Operations.CardId = #CardId
AND (Operations.[Output] = 0 AND Operations.RecipientId = #DepartId OR Operations.Input = 0 AND Operations.SenderId = #DepartId)
AND (#Date IS NULL OR Operations.[Date] <= #Date)
RETURN #Res
END
GO
It counts remains for certain product on certain department on certain date.
If it is less then zero it means something's wrong with database
Now I need to find all remains for each card, for each department for all dates in database where result is wrong.
Theoretically speaking we can fing this by calling this procedure in a query like this
SELECT DISTINCT Operations.[Date] as [Date],
Departments.Id as Depart,
Cards.Id as [Card],
[dbo].[GetCardDepartRemains] (Cards.Id, Departments.Id,Operations.[Date],NULL) as Res
FROM [jewellery].[dbo].[Cards]
CROSS JOIN [jewellery].[dbo].[Departments]
CROSS JOIN [jewellery].[dbo].[Operations]
WHERE [dbo].[GetCardDepartRemains] (Cards.Id, Departments.Id,Operations.[Date],NULL) = -1
But this query executes more than 2 minutes, so we need to write a new query.
My query can find all remains for each card on each department on certain date (ex. '2016-10-04')
SELECT
[Card],
Depart,
Res
FROM
(SELECT
Cards.Id as [Card],
Departments.Id as Depart,
ISNULL(SUM(CASE WHEN Operations.[Output] = 0 AND Operations.RecipientId = Departments.Id THEN 1 ELSE -1 END), 0) as Res
FROM Operations
CROSS JOIN Cards
CROSS JOIN Departments
WHERE Operations.CardId = Cards.Id
AND (Operations.[Output] = 0 AND Operations.RecipientId = Departments.Id OR Operations.Input = 0 AND Operations.SenderId = Departments.Id)
AND (Operations.[Date] <= '2016-10-04')
GROUP BY Cards.Id, Departments.Id
) as X
WHERE Res = -1
Can you help to re-write this query to find remains for all dates?
Assuming SQL Server is 2008 or above:
To find all dates, just comment out the date filter like this:
-- AND (Operations.[Date] <= '2016-10-04')
If you need to filter on a date range:
AND (Operations.[Date] between between getDate()-30 and getDate()
Changing -30 to however many days in the past. So a year ago would be -364.

Passing parameter to divide field value to stored procedure makes it slow

Recently I made a change to my stored procedure to pass a parameter value to divide the value of one of the fields... and stored procedure became very slow. It was taking 1 minute to run 600 recs and now its taking 8-9 minutes to run same results. Could you please help improve this little change?
I only added the following line in the select list
(nullif(x.tardies, 0) / #addtardies) addtardies
Here's the complete code:
ALTER PROCEDURE [dbo].[z_testCalc]
(
#calendarID int,
#grade varchar(3),
#AbsType varchar(1),
#Tardies varchar(1),
#startDate smallDateTime,
#endDate smallDateTime,
#TeamActivity varchar(50),
#Percent VARCHAR(10),
#AddTardies int
)
AS
BEGIN
SET NOCOUNT ON;
select distinct
x.test1,
x.test2,
x.AbsType,
x.UnexAbs,
x.ExAbs,
x.Tardies,
mp.meetings,
round((1 - cast(x.UnexAbs as decimal(6,3))/cast(mp.meetings as decimal(6,3))) * 100,1) percentPres,
**(nullif(x.tardies, 0) / #addtardies) addtardies,**
x.endDate
from
(SELECT DISTINCT
sch.test1,
p.test1,
case when #AbsType = 'T' then 'Unexc, Exc' when #AbsType = 'U' then 'Unexc' else 'Exc' end 'AbsType',
sum(case when COALESCE(x.status, a.status) = 'A' and CASE WHEN a.excuseID IS NOT NULL THEN x.excuse ELSE a.excuse END = 'U' then 1 else 0 end) 'UnexAbs',
sum(case when COALESCE(x.status, a.status) = 'A' and CASE WHEN a.excuseID IS NOT NULL THEN x.excuse ELSE a.excuse END = 'E' then 1 else 0 end) 'ExAbs',
sum(case when COALESCE(x.status, a.status) = 'T' then 1 else 0 end) 'Tardies',
ros.endDate
FROM
test1 a WITH (NOLOCK)
Thank you in advance.
This doesn't look to me like a likely scenario for huge parameter sniffing issues. Still, when introducing a parameter results in bizarre slowdowns like you describe, I always like to rule it out.
Try adding a line that assigns your parameter to a local variable, and then use the local variable, not the parameter, in your query.
so put this at the top, before the query...
DECLARE #AddTardies_lcl int
SET #AddTardies_lcl = #AddTardies
and then use #AddTardies_lcl in your new logic.
(nullif(x.tardies, 0) / #AddTardies_lcl) addtardies
hope it helps!

Case Statement Sql Multiple column check for dates

I have a stored procdure that uses case statement as follows: What I am trying to do is evaluate 2 columns in the testTable for dates. So the below case statement says that if stop_date is null or greater than current date then set is_active cloumn is Y else N
What I am trying to do is also evaluate another date column say another_stop_date and check if it is null or has a date greater then today and use same logic to update the is_active column
I am not sure if we can use multiple case statement logic to update a single column?
I have commented the code below where I am not getting the right results
Basically need to evaluate stop_dt and another_stop_date columns from testTable!
USE [Test]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROC [dbo].[p_test]
#Blank_Row CHAR(1) = 'N'
AS
BEGIN
SET NOCOUNT ON
DECLARE #TD DATETIME
SELECT #TD = GETDATE()
DECLARE #tempTable TABLE (
ID INT,
c_id INT,
desc varchar(40),
date datetime,
s_col TinyINT,
is_active char(1),
stuff VARCHAR(8))
INSERT INTO #tempTable
SELECT id, c_id, desc, max( date ), 1,
CASE WHEN (stop_dt IS NULL OR stop_dt > #TD) THEN 'Y'
--//Case When (another_stop_date is NULL or another Stop_date > #TD) THEN 'Y'<-----confused here
ELSE 'N' END,
stuff
FROM testTable
GROUP BY id, stop_dt, c_id, desc, stuff, another_stop_date
Select * from tempTable
You can combine clauses in a case statement with the usual logical operators, as well as having separate cases:
Case
When
(stop_dt is null or stop_dt > #td) and
(another_stop_date is null or another_stop_date > #td)
Then 'Y'
Else 'N'
End
Case statement operate close to if statements and can have multiple clauses.
Case when condition_1 > 1 then 'hi'
when condition_1 < 14 then 'no'
when condition_89 > 12 then 'why is this here'
else 1
end
Apply it to your statement:
CASE WHEN (stop_dt IS NULL OR stop_dt > #TD) THEN 'Y'
When (another_stop_date is NULL or another Stop_date > #TD) THEN 'Y'<-----confused here
ELSE 'N' END