SQL Table-Valued User-Defined Functions between TWo dates - sql

I need to build a table valued user defined function using two parameters
input like select * from func_1('01/01/2012','09/09/2015');
output should be like :
month quator semi_annual annual
1 1 1 2012
2 1 1 2012
3 1 1 2012
4 2 1 2012
5 2 1 2012
6 2 1 2012
7 3 2 2012
. . . ...
. . . ....
upto
9 3 2 2015
I need a table valued function for this.
i tried a code like this
create function func3_D_D
(#startDate date, #endDate date)
RETURNS #dates table
(months int,quatorly int,Semi_anuual int,Annual int)
As
Begin
declare
#months int,
#quatorly int,
#Semi_anuual int,
#Annual int;
select #months= DATEDIFF(MONTH, #startDate, #endDate);
select #quatorly= DATEDIFF(QUARTER, #startDate, #endDate);
select #Semi_anuual= DATEDIFF(QUARTER, #startDate, #endDate)/ 2;
select #Annual= DATEDIFF(YEAR, #startDate, #endDate);
WHILE (#endDate > #startDate)
begin
insert into #dates
select #months,#quatorly,#Semi_anuual,#Annual;
End;
return;
End;

Using a loop for this is about a awful for performance as you can get. You need to use a tally table for this type of thing. You need to start thinking in sets instead of row by row. Once you have a tally table (which I generated here with a cte), this is pretty simple.
create function MyFunctionThatGetsDatesByRange
(
#StartDate DATE
, #EndDate DATE
) RETURNS TABLE WITH SCHEMABINDING AS RETURN
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select DATEADD(DAY, N - 1, #StartDate) as date
, DATEPART(MONTH, DATEADD(DAY, N - 1, #StartDate)) as Month
, DATEPART(QUARTER, DATEADD(DAY, N - 1, #StartDate)) as Quarter
, CASE when DATEPART(QUARTER, DATEADD(DAY, N - 1, #StartDate)) <= 2 then 1 else 2 end as SemiAnnual
, DATEPART(YEAR, DATEADD(DAY, N - 1, #StartDate)) as Annual
from cteTally
where N <= DATEDIFF(DAY, #StartDate, #EndDate);
GO
declare #StartDate date = '2012-01-01'
, #EndDate date = '2015-09-09';
select *
from dbo.MyFunctionThatGetsDatesByRange(#StartDate, #EndDate)

hi thanks for your reply i achived that by using the below code.
create function func5_D_D
(#startDate date, #endDate date)
RETURNS #dates table
(months int,
quators int,
semi_annual int,
annual int)
As
Begin
WHILE #startDate <= #endDate
BEGIN
INSERT INTO #dates(months,quators,semi_annual,annual) values (MONTH(#startDate), datepart(qq,#startDate),
case when datepart(qq,#startDate) in (1,2) then 1 else
2 End,datepart(yyyy,#startdate))
SET #startDate = DATEADD(MONTH,1,#startDate)
END
return;
end;

Difference between two dates in X years Y Months and Z days (For example: Age in Years, Months and days)
We can use a script like below to get the difference between two dates in Years, Months and days.
Difference between two dates in X years Y Months and Z days (For example: Age in Years, Months and days)
We can use a script like below to get the difference between two
datDECLARE #FromDate DATETIME = '2010-01-01 23:59:59.000',
#ToDate DATETIME = '2015-01-02 00:00:00.000',
#Years INT, #Months INT, #Days INT, #tmpFromDate DATETIME
SET #Years = DATEDIFF(YEAR, #FromDate, #ToDate)
- (CASE WHEN DATEADD(YEAR, DATEDIFF(YEAR, #FromDate, #ToDate),
#FromDate) > #ToDate THEN 1 ELSE 0 END)
SET #tmpFromDate = DATEADD(YEAR, #Years , #FromDate)
SET #Months = DATEDIFF(MONTH, #tmpFromDate, #ToDate)
- (CASE WHEN DATEADD(MONTH,DATEDIFF(MONTH, #tmpFromDate, #ToDate),
#tmpFromDate) > #ToDate THEN 1 ELSE 0 END)
SET #tmpFromDate = DATEADD(MONTH, #Months , #tmpFromDate)
SET #Days = DATEDIFF(DAY, #tmpFromDate, #ToDate)
- (CASE WHEN DATEADD(DAY, DATEDIFF(DAY, #tmpFromDate, #ToDate),
#tmpFromDate) > #ToDate THEN 1 ELSE 0 END)
SELECT #FromDate FromDate, #ToDate ToDate,
#Years Years, #Months Months, #Days Days
s in Years, Months and days.

Related

Get the remaining days between two dates

I am trying to calculate the remaining year, month and days between 2 dates using the method below, but I cannot get the remaining dates right. Is there a function or different method I should try?
Select (DATEDIFF(m, '1965-10-17', '2021-04-07')) /12 as AgeYr ,
(DATEDIFF(m, '1965-10-17', '2021-04-07')) %12 as AgeMth,
(DATEDIFF(d, '1965-10-17', '2021-04-07')) %30 as AgeDay;
This is the result of my code
AgeYr
AgeMth
AgeDay
55
6
10
but the result should be
AgeYr
AgeMth
AgeDay
55
6
21
DECLARE #date datetime, #startdate datetime, #enddate datetime, #years int, #months int, #days int
SELECT #startdate = '1965-10-17'
SELECT #enddate = '2021-04-07'
SELECT #date = #startdate
SELECT #years = DATEDIFF(yy, #startdate, #enddate) - CASE WHEN (MONTH(#date) > MONTH(#enddate)) OR (MONTH(#date) = MONTH(#enddate) AND DAY(#date) > DAY(#enddate)) THEN 1 ELSE 0 END
SELECT #startdate = DATEADD(yy, #years, #startdate)
SELECT #months = DATEDIFF(m, #startdate, #enddate) - CASE WHEN DAY(#date) > DAY(#enddate) THEN 1 ELSE 0 END
SELECT #startdate = DATEADD(m, #months, #startdate)
SELECT #days = DATEDIFF(d, #startdate, #enddate)
SELECT #years years, #months months, #days days
Output:
AgeYr
AgeMth
AgeDay
55
5
21
db<>fiddle here
You can also do it with common table expression:
;with years as
(SELECT (DATEDIFF(yy, '1965-10-17', '2021-04-07') - CASE WHEN (MONTH('1965-10-17') > MONTH('2021-04-07')) OR (MONTH('1965-10-17') = MONTH('2021-04-07') AND DAY('1965-10-17') > DAY('2021-04-07')) THEN 1 ELSE 0 END)AgeYr,
'1965-10-17' startdate)
,months as
(SELECT (DATEDIFF(m, DATEADD(yy, years.AgeYr, '1965-10-17'), '2021-04-07') - CASE WHEN DAY('1965-10-17') > DAY('2021-04-07') THEN 1 ELSE 0 END)AgeMth, DATEADD(yy, years.AgeYr, '1965-10-17') temp
from years)
,days as
(select DATEDIFF(d, DATEADD(m, months.AgeMth, temp), '2021-04-07')AgeDay from months)
select years.AgeYr,months.AgeMth,days.AgeDay from years,months,days
Output:
AgeYr
AgeMth
AgeDay
55
5
21
db<>fiddle here

Difference in Days Between Two Dates with Years, Months, and Days

I'm not a SQL expert, would you know if it's possible to have a query provide Days Between Two Dates to count days outstanding with today's date and output: Years, Months, Days and outstanding days providing 30 Days, 60 Days, 90 Days?
If I Declare "Years, Months, Days" from "Check_Date" and "Created_Date".
SQL provides a separate window, and, I only see Declared "FromDate" time
with "ToDate" displaying total Years, Months, Days. I am looking for a way
to have results provide a roll BY "Created_Date" records and include:
Years, Months, Days and outstanding days providing 30 Days, 60 Days, 90 Days? Can you suggest something with a similar result?
Scrip Code:
DECLARE #FromDate DATETIME = '2015-01-01 00:00:00',
#ToDate DATETIME = '2019-09-18 00:00:00',
#Years INT, #Months INT, #Days INT, #tmpFromDate DATETIME
SET #Years = DATEDIFF(YEAR, #FromDate, #ToDate)
- (CASE WHEN DATEADD(YEAR, DATEDIFF(YEAR, #FromDate, #ToDate),
#FromDate) > #ToDate THEN 1 ELSE 0 END)
SET #tmpFromDate = DATEADD(YEAR, #Years , #FromDate)
SET #Months = DATEDIFF(MONTH, #tmpFromDate, #ToDate)
- (CASE WHEN DATEADD(MONTH,DATEDIFF(MONTH, #tmpFromDate, #ToDate),
#tmpFromDate) > #ToDate THEN 1 ELSE 0 END)
SET #tmpFromDate = DATEADD(MONTH, #Months , #tmpFromDate)
SET #Days = DATEDIFF(DAY, #tmpFromDate, #ToDate)
- (CASE WHEN DATEADD(DAY, DATEDIFF(DAY, #tmpFromDate, #ToDate),
#tmpFromDate) > #ToDate THEN 1 ELSE 0 END)
SELECT #FromDate FromDate, #ToDate ToDate,
#Years Years, #Months Months, #Days Days
SELECT DISTINCT
ge.Name, --table columns
ge.Entity_Type,
ge.Entity_Number,
bc.Super_Entity_ID,
ch.Check_Date, --check created
ch.Created_Date, --if payment was received
ch.Check_Number,
ch.Amount,
vn.Vendor_Name
Check_Date,Created_Date,DATEDIFF("DAY",Check_Date,Created_Date) AS DAY
FROM dbo.gl_entities AS ge
INNER JOIN
dbo.super_entity AS se
ON ge.Super_Entity_ID = se.Super_Entity_ID
INNER JOIN
dbo.bank_codes AS bc
ON se.Super_Entity_ID = bc.Super_Entity_ID
INNER JOIN
dbo.checks AS ch
ON bc.Bank_Code_ID = ch.Bank_Code_ID
INNER JOIN
dbo.vendors AS vn
ON ch.Vendor_ID = vn.Vendor_ID
WHERE
ge.Active = 1 and vn.active = 1 and (ge.IS_Shadow = 1 OR se.IS_Tiered = 0)
AND CHECK_DATE > '20150101 00:00:00'
AND CHECK_DATE< '20190918 00:00:00'
ORDER BY ch.Check_Date, ch.Created_Date
To simply repeat them in their own columns in the query:
DECLARE #FromDate DATETIME = '2015-01-01 00:00:00',
#ToDate DATETIME = '2019-09-18 00:00:00',
#Years INT, #Months INT, #Days INT, #tmpFromDate DATETIME
SET #Years = DATEDIFF(YEAR, #FromDate, #ToDate)
- (CASE WHEN DATEADD(YEAR, DATEDIFF(YEAR, #FromDate, #ToDate),
#FromDate) > #ToDate THEN 1 ELSE 0 END)
SET #tmpFromDate = DATEADD(YEAR, #Years , #FromDate)
SET #Months = DATEDIFF(MONTH, #tmpFromDate, #ToDate)
- (CASE WHEN DATEADD(MONTH,DATEDIFF(MONTH, #tmpFromDate, #ToDate),
#tmpFromDate) > #ToDate THEN 1 ELSE 0 END)
SET #tmpFromDate = DATEADD(MONTH, #Months , #tmpFromDate)
SET #Days = DATEDIFF(DAY, #tmpFromDate, #ToDate)
- (CASE WHEN DATEADD(DAY, DATEDIFF(DAY, #tmpFromDate, #ToDate),
#tmpFromDate) > #ToDate THEN 1 ELSE 0 END)
SELECT DISTINCT
ge.Name, --table columns
ge.Entity_Type,
ge.Entity_Number,
bc.Super_Entity_ID,
ch.Check_Date, --check created
ch.Created_Date, --if payment was received
ch.Check_Number,
ch.Amount,
vn.Vendor_Name,
Check_Date,Created_Date,DATEDIFF("DAY",Check_Date,Created_Date) AS DAY,
#FromDate FromDate, #ToDate ToDate, #Years Years, #Months Months, #Days Days
FROM
. . .
If you run into problems with the DISTINCT clause, you can always use a subquery. If you want Years, Months, Days to be different based on one of the columns, you'll have to elaborate and then we can move everything you did in the SET statements into the SELECT statement.
Without your tables and data I cannot test very well.
pseudocode to try and demonstrate moving set statements to select statements
declare #begin date = '2018-01-01'
declare #end date = '2019-01-01'
declare #middletest int = datediff("dd", #begin, #end)/2
declare #middledate date = dateadd("dd", #middletest, #begin)
declare #middlemonth int = month(#middledate)
select #middlemonth half_month, #middledate middle_date
-- ,other_columns here
from table
where something
Start replacing.
#middlemonth is in the select statement so replace it with month(#middledate).
declare #begin date = '2018-01-01'
declare #end date = '2019-01-01'
declare #middletest int = datediff("dd", #begin, #end)/2
declare #middledate date = dateadd("dd", #middletest, #begin)
select month(#middledate) half_month, #middledate middle_date
-- ,other_columns here
from table
where something
#middledate is now in the select statement in 2 places so replace it with dateadd("dd", #middletest, #begin) each time.
declare #begin date = '2018-01-01'
declare #end date = '2019-01-01'
declare #middletest int = datediff("dd", #begin, #end)/2
select month(dateadd("dd", #middletest, #begin)) half_month,
dateadd("dd", #middletest, #begin) middle_date
-- ,other_columns here
from table
where something
Continue along.
declare #begin date = '2018-01-01'
declare #end date = '2019-01-01'
select month(dateadd("dd", datediff("dd", #begin, #end)/2, #begin)) half_month,
dateadd("dd", datediff("dd", #begin, #end)/2, #begin) middle_date
-- ,other_columns here
from table
where something
And you can, as desired, replace things with values from the table. If the table has a column start_date and you want that to be the begin and then use the current date as the end:
select month(dateadd("dd", datediff("dd", start_date, getdate())/2, start_date)) half_month,
dateadd("dd", datediff("dd", start_date, getdate())/2, start_date) middle_date
-- ,other_columns here
from table
where something

Number of Weeks between Parameter Dates - Group by Week - SQL

I have 2 parameter in my stored procedure #FromDate and #ToDate, I want to work out the number of weeks between #FromDate and #ToDate, #FromDate is always a Monday while #ToDate is always a Sunday but the dates could cover weeks, months etc.
Currently I have DATEDIFF(WW, #FromDate, #ToDate)
Which returns say 4 but I would like it to return 1,2,3 and 4 in 4 rows rather than just 1 row.
What is the best way to achieve this?
If I've got it right:
With T( Number ) as
(
Select 0 as Number
union all
Select Number + 1
from T
where Number < DATEDIFF(WW, #FromDate, #ToDate)
)
SELECT NUMBER FROM T WHERE Number>0;
SQLFiddle demo
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = GETUTCDATE() - 90
SET #EndDate = GETUTCDATE()
DECLARE #WeekNumber INT
SET #WeekNumber = DATEDIFF(WW, #StartDate, #EndDate) ;
WITH CTE_WeekNumber ( WeekNumber )
AS ( SELECT 1
WHERE #WeekNumber >= 1
UNION ALL
SELECT WeekNumber + 1
FROM CTE_WeekNumber
WHERE #WeekNumber >= WeekNumber + 1
)
SELECT *
FROM CTE_WeekNumber

How can I rewrite this as a select statement using group by instead of using a loop

I am revisiting some old code I wrote for a report when I was still very new to SQL (MSSQL). It does what it is supposed to but its not the prettiest or most efficient.
The dummy code below mimics what I currently have in place. Here I am trying to get counts for the number of contracts that are open over the last 5 weeks. For this example a contract is considered open if the start date of the contract happens before of during the given week and the end date happens during or after the given week.
dbo.GetWeekStart(#Date DATETIME, #NumOfWeeks INT, #FirstDayOfWeek CHAR(3)) is a function that will return the first day of each week based on the date provided for a specified number of weeks. ie SELECT * FROM dbo.GetWeekStart('20120719', -2, 'MON') will return the 2 mondays prior to July 19, 2012.
How can I simplify this? I think there is someone to do this without a loop but I have not been able to figure it out.
DECLARE #RunDate DATETIME,
#Index INT,
#RowCount INT,
#WeekStart DATETIME,
#WeekEnd DATETIME
DECLARE #Weeks TABLE
(
WeekNum INT IDENTITY(0,1),
WeekStart DATETIME,
WeekEnd DATETIME
)
DECLARE #Output TABLE
(
WeekStart DATETIME,
OpenContractCount INT
)
SET #RunDate = GETDATE()
INSERT INTO #Weeks (WeekStart, WeekEnd)
SELECT WeekStart,
DATEADD(ss,-1,DATEADD(ww,1,WeekStart))
FROM dbo.[GetWeekStart](#RunDate, -5, 'MON')
SET #RowCount = (SELECT COUNT(*) FROM #Weeks)
SET #Index = 0
WHILE #Index < #RowCount
BEGIN
SET #WeekStart = (SELECT WeekStart FROM #Weeks WHERE WeekNum = #Idx)
SET #WeekEnd = (SELECT WeekEnd FROM #Weeks WHERE WeekNum = #Idx)
INSERT INTO #Output (WeekStart, OpenContractCount)
SELECT #WeekStart,
COUNT(*)
FROM Contracts c
WHERE c.StartDate <= #WeekEnd
AND ISNULL(c.EndDate, GETDATE()) >= #WeekStart
SET #Index = #Index + 1
END
SELECT * FROM #Output
I see no reason why this wouldn't work:
DECLARE #RunDate DATETIME = GETDATE()
SELECT WeekStart, COUNT(*)
FROM Contracts c
INNER JOIN dbo.[GetWeekStart](#RunDate, -5, 'MON')
ON c.StartDate < DATEADD(WEEK, 1, WeekStart)
AND (c.EndDate IS NULL OR c.EndDate >= #WeekStart)
GROUP BY WeekStart
I am not sure how you are generating your dates within your function, just in case you are using a loop/recursive CTE I'll include a query that doesn't use loops/cursors etc.
DECLARE #RunDate DATETIME = GETDATE()
-- SET DATEFIRST AS 1 TO ENSURE MONDAY IS THE FIRST DAY OF THE WEEK
-- CHANGE THIS TO SIMULATE CHANGING YOUR WEEKDAY INPUT TO db
SET DATEFIRST 1
-- SET RUN DATE TO BE THE START OF THE WEEK
SET #RunDate = CAST(DATEADD(DAY, 1 - DATEPART(WEEKDAY, #RunDate), #RunDate) AS DATE)
;WITH Weeks AS
( SELECT TOP 5 -- CHANGE THIS TO CHANGE THE WEEKS TO RUN
DATEADD(WEEK, 1 - ROW_NUMBER() OVER(ORDER BY Object_ID), #RunDate) [WeekStart]
FROM sys.All_Objects
)
SELECT WeekStart, COUNT(*)
FROM Contracts c
INNER JOIN Weeks
ON c.StartDate < DATEADD(WEEK, 1, WeekStart)
AND (c.EndDate IS NULL OR c.EndDate >= #WeekStart)
GROUP BY WeekStart
Did this quick but it should work
/*CTE generates Start & End Dates for 5 weeks
Start Date = Sunday of week # midnight
End Date = Sunday of next week # midnight
*/
WITH weeks
AS ( SELECT DATEADD(ww, -4,
CAST(FLOOR(CAST(GETDATE() - ( DATEPART(dw,
GETDATE()) - 1 ) AS FLOAT)) AS DATETIME)) AS StartDate
UNION ALL
SELECT DATEADD(wk, 1, StartDate)
FROM weeks
WHERE DATEADD(wk, 1, StartDate) <= GETDATE()
)
SELECT w.StartDate ,
COUNT(*) AS OpenContracts
FROM dbo.Contracts c
LEFT JOIN weeks w ON c.StartDate < DATEADD(d, 7, w.StartDate)
AND ISNULL(c.EndDate, GETDATE()) >= w.StartDate
GROUP BY w.StartDate

Return number of weekdays between 2 dates in T-SQL

I have 2 dates and want to know how many weekdays (mon-fri) there are
e.g.
thu jan 1 20xx
fri jan 2 20xx
sat jan 3 20xx
sun jan 4 20xx
mon jan 5 20xx
jan 1, jan 5 would return 3
(can ignore public holidays)
Try
DateDiff(day, #DtA, #DtB) - 2 * DateDiff(Week, #DtA, #DtB)
this may not work exactly, but you can see the idea. Some slight modification will work.
Assuming the dates can't be more than five and a half years from each other (or use your own tally table instead of master..spt_values):
DECLARE #date1 datetime, #date2 datetime;
SET #date1 = '20110901';
SET #date2 = '20110905';
SELECT COUNT(*)
FROM (
SELECT
Date = DATEADD(day, number, #date1)
FROM master..spt_values
WHERE type = 'P'
AND number between 0 AND DATEDIFF(day, #date1, #date2)
) s
WHERE DATENAME(DW, Date) NOT IN ('Saturday', 'Sunday')
try this:
SET DATEFIRST 1
DECLARE #StartDate datetime
,#EndDate datetime
SELECT #StartDate='6/21/2011'
,#EndDate='6/28/2011'
;with AllDates AS
(
SELECT #StartDate AS DateOf, datepart(weekday,getdate()) AS WeekDayNumber
UNION ALL
SELECT DateOf+1, datepart(weekday,DateOf+1)
FROM AllDates
WHERE DateOf<#EndDate
)
SELECT COUNT(*) AS WeekDayCount FROM AllDates WHERE WeekDayNumber<=5
OUTPUT:
WeekDayCount
------------
6
(1 row(s) affected)
If you have a holiday table, you can join it in and remove those as well.
EDIT based on #Ross Watson comment:
SET DATEFIRST 1
DECLARE #StartDate datetime
,#EndDate datetime
SELECT #StartDate='6/21/2011'
,#EndDate='6/28/2011'
;with AllDates AS
(
SELECT #StartDate AS DateOf, datepart(weekday,getdate()) AS WeekDayNumber
UNION ALL
SELECT DateOf+1, (WeekDayNumber+1) % 7
FROM AllDates
WHERE DateOf<#EndDate
)
SELECT COUNT(*) AS WeekDayCount FROM AllDates WHERE WeekDayNumber>0 AND WeekDayNumber<6
--I don't like using "BETWEEN", ">", ">=", "<", and "<=" are more explicit in defining end points
produces same output as original query.
Try the following:
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '2011/06/01'
SET #EndDate = '2011/06/31'
SELECT
(DATEDIFF(dd, #StartDate, #EndDate) + 1) -
(DATEDIFF(wk, #StartDate, #EndDate) * 5) -
(
CASE
WHEN DATENAME(dw, #StartDate) in
('Sunday', 'Tuesday', 'Wednesday', 'Turesday', 'Saturday')
THEN 1
ELSE 0
END
) -
(
CASE
WHEN DATENAME(dw, #EndDate) in
('Sunday', 'Tuesday', 'Wednesday', 'Turesday', 'Saturday')
THEN 1
ELSE 0
END
)
Also see if this is helpful
http://blog.sqlauthority.com/2007/06/08/sql-server-udf-function-to-display-current-week-date-and-day-weekly-calendar/
This approach is limited to ~100 days due to recursion. This works for the date ranges i've tested. Same idea above, removed the math and simplified:
BEGIN
SET DATEFIRST 1
DECLARE #StartDate datetime
,#EndDate datetime
SELECT #StartDate='12/16/2015'
,#EndDate='1/8/2016'
;with AllDates AS
(
SELECT #StartDate AS DateOf
UNION ALL
SELECT DateOf+1
FROM AllDates
WHERE DateOf<#EndDate
)
SELECT COUNT(*) AS WeekDayCount
FROM
AllDates
WHERE
datepart(weekday,DateOf) between 1 AND 5
--SELECT DateOf [date], datepart(weekday,DateOf) [day]
--FROM
-- AllDates
--WHERE
-- datepart(weekday,DateOf) between 1 AND 5
END