Need days in year month and days - sql

I have a table called Dates
Opendate Closedate
2015-07-09 NULL
2017-01-25 NULL
I want to have the output as
Opendate Workingperiod
2015-07-09 1 years 8 months 20 days
2017-01-25 0 years 1 months 3 days
We need to calculate the difference between opendate and today's date and in year month and days format.
have tried
SELECT Opendate,
CAST(DATEDIFF(month,Opendate,GETDATE())/12 AS VARCHAR(5))+' year '+
CAST(DATEDIFF(month,Opendate,GETDATE())%12 AS VARCHAR(5)) +' month '+
CAST(DATEDIFF(day,DATEADD(month,DATEDIFF(month,Opendate,GETDATE()),Opendate),GETDATE()) AS VARCHAR(5))+' days ' AS Workingperiod
FROM Dates
Output:-
Opendate Workingperiod
2015-07-09 1 year 8 month -8 days
2017-01-25 0 year 2 month -24 days
I am getting days in negative, can someone tell what is wrong in it.

DECLARE #opendate datetime, #date datetime, #years int, #months int, #days int
SELECT #opendate = '2015-07-09'
SELECT #date = #opendate
SELECT #years = DATEDIFF(YYYY, #date, GETDATE()) - CASE WHEN (MONTH(#opendate) > MONTH(GETDATE()))
OR (MONTH(#opendate) = MONTH(GETDATE()) AND DAY(#opendate) > DAY(GETDATE()))
THEN 1 ELSE 0 END
SELECT #date = DATEADD(YYYY, #years, #date)
SELECT #months = DATEDIFF(MONTH, #date, GETDATE()) - CASE WHEN DAY(#opendate) > DAY(GETDATE())
THEN 1 ELSE 0 END
SELECT #date = DATEADD(MONTH, #months, #date)
SELECT #days = DATEDIFF(DAY, #date, GETDATE())
SELECT #years AS 'YEARS', #months AS 'MONTHS', #days AS 'DAYS'
FIRST ENTRY (2015-07-09)
SECOND ENTRY (2017-01-25)

Related

Calculate quarter for dates given an example end date and quarter number

I have an issue where I need to determine fiscal quarters, but won't always know the start/end dates for the quarters. They will, however, always be 3 months long. What I will know is the ending date of the current quarter, and what quarter and year that refers to. For example, I might be given:
Current Quarter: Q4
Current Year: 2021
Current Quarter End Date: 1/31/2021
How can I get the quarter for any other date? If any of those 3 values were to change, the query still needs to provide the quarter for any given date based on those 3 parameters.
I came up with the following, which puts the last 4 years into a temp table:
DECLARE #QuarterEnd DATE = '1/31/2022'
, #CurrentQuarter INT = 1
, #CurrentYear INT = 2022
, #Counter INT = 16
, #qs INT = 0
, #qe INT = 2
, #DateToTest DATE = '12/15/2021'
CREATE TABLE #Quarters (
StartDate DATE
, EndDate DATE
, Qtr INT
, Yr INT
)
WHILE #Counter <> 0
BEGIN
INSERT INTO #Quarters VALUES (
cast(DATEADD(MONTH, DATEDIFF(MONTH, 0, #QuarterEnd)-#qe , 0) as date)
, cast(DATEADD(MONTH, DATEDIFF(MONTH, -1, #QuarterEnd)-#qs, -1) as date)
, #CurrentQuarter
, #CurrentYear
)
SET #Counter = #Counter - 1
SET #qs = #qs + 3
SET #qe = #qe + 3
SET #CurrentQuarter = CASE WHEN #CurrentQuarter = 1 THEN 4 ELSE #CurrentQuarter - 1 END
SET #CurrentYear = CASE WHEN #CurrentQuarter = 4 THEN #CurrentYear - 1 ELSE #CurrentYear END
END
SELECT #DateToTest
, (SELECT CONCAT('Q', Qtr, ' ', Yr) FROM #Quarters WHERE #DateToTest BETWEEN StartDate and EndDate)
FROM #Quarters
However, this doesn't seem to be practical when I'm running queries that will return hundreds of thousands of records.
I suppose I can throw that into a function and call it with:
SELECT MyQuarter = dbo.MyQuarterFunction(#QuarterEnd, #CurrentQuarter, #CurrentYear, #DateToTest)
There has to be a more efficient way to do this. Any suggestions?
Just create a permanent table called Quarters.
CREATE TABLE dbo.Quarters
(
StartDate date,
QuarterNumber tinyint,
FiscalYear int,
NextQuarterStartDate AS (DATEADD(MONTH, 3, StartDate))
);
INSERT dbo.Quarters(StartDate, QuarterNumber, FiscalYear)
VALUES('20200201',1,2020),
('20200501',2,2020),
('20200801',3,2020),
('20201101',4,2020),
('20210201',1,2021),
('20210501',2,2021),
('20210801',3,2021),
('20211101',4,2021),
('20220201',1,2022),
('20220501',2,2022),
('20220801',3,2022),
('20221101',4,2022);
Now any time you are given a date (like GETDATE()) you can find the other information easily:
DECLARE #date date = GETDATE();
SELECT * FROM dbo.Quarters
WHERE #date >= StartDate
AND #date < NextQuarterStartDate;
Example db<>fiddle
If you need to support multiple fiscal calendars simultaneously, just add a column (like CalendarID or CompanyID or CustomerID).
And really, you don't even need a calendar or quarters table for this. You already have a table of clients, right? Just add a column to store what month their fiscal year starts. That's really all you need.
CREATE TABLE dbo.Clients
(
ClientID int NOT NULL CONSTRAINT PK_Clients PRIMARY KEY,
Name nvarchar(200) NOT NULL CONSTRAINT UQ_ClientName UNIQUE,
FiscalYearStart tinyint NOT NULL CONSTRAINT CK_ValidMonth
CHECK (FiscalYearStart BETWEEN 1 AND 12)
);
Now let's insert a few rows with some clients with different fiscal years:
INSERT dbo.Clients(ClientID, Name, FiscalYearStart)
VALUES(1, N'ClientFeb', 2), -- fiscal year starts in February
(2, N'ClientMay', 5), -- fiscal year starts in May
(3, N'ClientNormal', 1); -- fiscal year matches calendar
Now, yes, we need a function, but let's not do any while loops or counters or #temp tables.
CREATE FUNCTION dbo.GetLast16Quarters
(
#DateToTest date,
#ClientID int
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
(
WITH n(n) AS
(
SELECT n = 1 UNION ALL
SELECT n + 1 FROM n WHERE n < 20
),
Last20Quarters(QuarterStart, FiscalYearStart) AS
(
SELECT QuarterStart = DATEADD(QUARTER, 1-n,
DATEFROMPARTS(YEAR(#DateToTest)+1, FiscalYearStart, 1)),
FiscalYearStart
FROM dbo.Clients CROSS JOIN n WHERE ClientID = #ClientID
),
Last16Quarters AS
(
SELECT TOP (16) QuarterStart,
y = YEAR(DATEADD(MONTH, 1-FiscalYearStart, QuarterStart))
FROM Last20Quarters WHERE QuarterStart < #DateToTest
ORDER BY QuarterStart DESC
)
SELECT QuarterStart,
QuarterEnd = EOMONTH(QuarterStart, 2),
FiscalYear = y,
QuarterNumber = ROW_NUMBER() OVER
(PARTITION BY y ORDER BY QuarterStart)
FROM Last16Quarters);
Then to call it:
DECLARE #DateToTest date = '20211215';
SELECT * FROM dbo.GetLast16Quarters(#DateToTest, 1);
Output:
QuarterStart
QuarterEnd
FiscalYear
QuarterNumber
2018-02-01
2018-04-30
2018
1
2018-05-01
2018-07-31
2018
2
2018-08-01
2018-10-31
2018
3
2018-11-01
2019-01-31
2018
4
2019-02-01
2019-04-30
2019
1
2019-05-01
2019-07-31
2019
2
2019-08-01
2019-10-31
2019
3
2019-11-01
2020-01-31
2019
4
2020-02-01
2020-04-30
2020
1
2020-05-01
2020-07-31
2020
2
2020-08-01
2020-10-31
2020
3
2020-11-01
2021-01-31
2020
4
2021-02-01
2021-04-30
2021
1
2021-05-01
2021-07-31
2021
2
2021-08-01
2021-10-31
2021
3
2021-11-01
2022-01-31
2021
4
Example db<>fiddle
Assuming that you have two input variables:
declare #quarter_end date = '2021-01-31';
declare #current_quarter int = 4;
You can calculate the first month of financial year:
declare #first_month_of_fy int = (month(#quarter_end) - #current_quarter * 3 + 12) % 12 + 1;
-- 2 i.e. February
And use that value to calculate the quarter and year for any date using some math:
select *
from (values
('2020-12-15'),
('2021-01-15'),
('2021-12-15'),
('2022-01-15')
) as t(testdate)
cross apply (select
(month(testdate) - #first_month_of_fy + 12) % 12 + 1
) as ca1(month_of_fy)
cross apply (select
(month_of_fy - 1) / 3 + 1,
year(dateadd(month, 12 - month_of_fy, dateadd(day, - day(testdate) + 1, testdate)))
) as ca2(fy_quarter, fy_year)
DB<>Fiddle
I ended up creating a function to handle this. Since I'm given the last day of the quarter, which quarter it is, and which year it is, I can determine the start and end date of that fiscal year. Since a quarter is always 3 months, I can also determine which months fall into which quarter.
The first 4 variables, #qa, #qb, #qc, #qd hold a comma separated list of the months within each quarter (#qa is current quarter, #qb is current quarter -1, #qc is current quarter -2, and #qd is current quarter -3)
The second 2 variables determine the first day and last day of the fiscal calendar
To get the quarter & year, I first get the month from the supplied date (#Date) and see if it's in #qa, #qb, #qc or #qd. That tells me the fiscal quarter.
Finally, I compare the given date to the start and end date of the current fiscal year, and to the 6 years prior (going back 6 years is enough for what I need)
CREATE FUNCTION [dbo].[FunctionNameHere]
(
#Date DATE
, #QuarterEnd DATE
, #CurrentQuarter INT
, #CurrentYear INT
)
RETURNS VARCHAR(7)
AS
BEGIN
DECLARE #qa VARCHAR(8) = (concat(datepart(m, dateadd(m, 0, #QuarterEnd)),',', datepart(m, dateadd(m, -1, #QuarterEnd)),',', datepart(m, dateadd(m, -2, #QuarterEnd))))
DECLARE #qb VARCHAR(8) = (concat(datepart(m, dateadd(m, -3, #QuarterEnd)),',', datepart(m, dateadd(m, -4, #QuarterEnd)),',', datepart(m, dateadd(m, -5, #QuarterEnd))))
DECLARE #qc VARCHAR(8) = (concat(datepart(m, dateadd(m, -6, #QuarterEnd)),',', datepart(m, dateadd(m, -7, #QuarterEnd)),',', datepart(m, dateadd(m, -8, #QuarterEnd))))
DECLARE #qd VARCHAR(8) = (concat(datepart(m, dateadd(m, -9, #QuarterEnd)),',', datepart(m, dateadd(m, -10, #QuarterEnd)),',', datepart(m, dateadd(m, -11, #QuarterEnd))))
DECLARE #YearStart DATE = DATEADD(d, 1, DATEADD(q, -#CurrentQuarter, #QuarterEnd))
DECLARE #YearEnd DATE = DATEADD(q, 4-#CurrentQuarter, #QuarterEnd)
DECLARE #Qtr VARCHAR(8) = CONCAT('Q', CASE WHEN DATEPART(m, #Date) IN (SELECT value FROM string_split(#qa, ',')) THEN #CurrentQuarter
WHEN DATEPART(m, #Date) IN (SELECT value FROM string_split(#qb, ',')) THEN CASE WHEN #CurrentQuarter = 1 THEN 4
WHEN #CurrentQuarter = 2 THEN 1
WHEN #CurrentQuarter = 3 THEN 2
WHEN #CurrentQuarter = 4 THEN 3 END
WHEN DATEPART(m, #Date) IN (SELECT value FROM string_split(#qc, ',')) THEN CASE WHEN #CurrentQuarter = 1 THEN 3
WHEN #CurrentQuarter = 2 THEN 4
WHEN #CurrentQuarter = 3 THEN 1
WHEN #CurrentQuarter = 4 THEN 2 END
WHEN DATEPART(m, #Date) IN (SELECT value FROM string_split(#qd, ',')) THEN CASE WHEN #CurrentQuarter = 1 THEN 2
WHEN #CurrentQuarter = 2 THEN 3
WHEN #CurrentQuarter = 3 THEN 4
WHEN #CurrentQuarter = 4 THEN 1 END
END,
' ',
CASE WHEN #Date BETWEEN #YearStart AND #YearEnd THEN #CurrentYear
WHEN #Date BETWEEN dateadd(Year, -1, #YearStart) AND dateadd(Year, -1, #YearEnd) THEN #CurrentYear - 1
WHEN #Date BETWEEN dateadd(Year, -2, #YearStart) AND dateadd(Year, -2, #YearEnd) THEN #CurrentYear - 2
WHEN #Date BETWEEN dateadd(Year, -3, #YearStart) AND dateadd(Year, -3, #YearEnd) THEN #CurrentYear - 3
WHEN #Date BETWEEN dateadd(Year, -4, #YearStart) AND dateadd(Year, -4, #YearEnd) THEN #CurrentYear - 4
WHEN #Date BETWEEN dateadd(Year, -5, #YearStart) AND dateadd(Year, -5, #YearEnd) THEN #CurrentYear - 5
WHEN #Date BETWEEN dateadd(Year, -6, #YearStart) AND dateadd(Year, -6, #YearEnd) THEN #CurrentYear - 6
ELSE 9999 END)
return #Qtr
END

How to get the week number, start date and end date of Compelete year in SQL Server?

How to get the complete week number, startdate and enddate of complete year.
Note
Week starts from Sunday to Saturday
at the same time week no 1 (ex: 01-01-2020 to 04-01-2020) and last week should be (ex:27-12-2020 to 31-12-2020)
Expecting result
WeekNo WeekStartDate WeekEndDate
===================================
1 2019-01-01 2019-01-05
2 2019-01-06 2019-01-12
3 2019-01-13 2019-01-19
4 2019-01-20 2019-01-26
5 2019-01-27 2019-02-02
6 2019-02-03 2019-02-09
7 2019-02-10 2019-02-16
8 2019-02-17 2019-02-23
9 2019-02-24 2019-03-02
...
...upto end of the year
Actually I tried this one also rextester
If you are interested in 2019 only, then the following code will produce exactly what you are looking for:
DECLARE #weekNum INT = 1;
WITH Weeks AS (
SELECT #weekNum AS WeekNo
UNION ALL
SELECT WeekNo + 1 FROM Weeks WHERE WeekNo + 1 <= 53
)
SELECT WeekNo,
CASE
WHEN WeekStartDate < '2019-01-01' THEN CONVERT(DATE, '2019-01-01')
ELSE CONVERT(DATE, WeekStartDate)
END AS WeekStartDate,
CASE
WHEN WeekEndDate > '2019-12-31' THEN CONVERT(DATE, '2019-12-31')
ELSE CONVERT(DATE, WeekEndDate)
END AS WeekEndDate
FROM (
SELECT WeekNo,
DATEADD(WEEK, WeekNo - 1, '2018-12-30') AS WeekStartDate,
DATEADD(WEEK, WeekNo - 1, '2019-01-05') AS WeekEndDate
FROM Weeks
) a
OUTPUT:
WeekNo WeekStartDate WeekEndDate
1 2019-01-01 2019-01-05
2 2019-01-06 2019-01-12
3 2019-01-13 2019-01-19
4 2019-01-20 2019-01-26
5 2019-01-27 2019-02-02
6 2019-02-03 2019-02-09
7 2019-02-10 2019-02-16
8 2019-02-17 2019-02-23
...
51 2019-12-15 2019-12-21
52 2019-12-22 2019-12-28
53 2019-12-29 2019-12-31
Edit following OP comment about variable start and end dates
Following OP's comment about varying start and end dates, I've revisited the code and made it such that is can work between any two dates:
DECLARE #startDate DATE = CONVERT(DATE, '2019-01-01');
DECLARE #endDate DATE = CONVERT(DATE, '2019-12-31');
DECLARE #weekNum INT = 1;
WITH Weeks AS (
SELECT #weekNum AS WeekNo
UNION ALL
SELECT WeekNo + 1 FROM Weeks WHERE WeekNo + 1 <= DATEDIFF(WEEK, #StartDate, #EndDate) + 1
)
SELECT WeekNo,
CASE
WHEN WeekStartDate < #startDate THEN #startDate
ELSE CONVERT(DATE, WeekStartDate)
END AS WeekStartDate,
CASE
WHEN WeekEndDate > #endDate THEN #endDate
ELSE CONVERT(DATE, WeekEndDate)
END AS WeekEndDate
FROM (
SELECT WeekNo,
DATEADD(WEEK, WeekNo - 1, OffsetStartDate) AS WeekStartDate,
DATEADD(WEEK, WeekNo - 1, OffsetEndDate) AS WeekEndDate
FROM Weeks
INNER JOIN (
SELECT CASE
WHEN DATEPART(WEEKDAY, #startDate) = 1 THEN #startDate
ELSE DATEADD(DAY, 1 - DATEPART(WEEKDAY, #startDate), #startDate)
END AS OffsetStartDate,
CASE
WHEN DATEPART(WEEKDAY, #startDate) = 1 THEN DATEADD(DAY, 6, #startDate)
ELSE DATEADD(DAY, 7 - DATEPART(WEEKDAY, #startDate), #startDate)
END AS OffsetEndDate
) a ON 1 = 1
) a
Simply modify #startDate and #endDate to reflect the desired start and end dates. The format of the string is YYYY-MM-DD.
This will output a variable number of weeks between the two dates, starting and ending on the specified date (creating partial weeks as needed). Hopefully, as per the requirement.
Slightly update on above answer of Martin. You can pass #startDate and #endDate based on your preference.
DECLARE #startDate DATETIME = '2019-01-01'
DECLARE #endDate DATETIME = '2021-01-01'
DECLARE #totalWeeks BIGINT= NULL
SELECT #totalWeeks =datediff(ww,#startdate,#enddate)
DECLARE #weekNum INT = 1;
WITH Weeks AS (
SELECT #weekNum AS WeekNo
UNION ALL
SELECT WeekNo + 1 FROM Weeks WHERE WeekNo + 1 <= #totalWeeks
)
SELECT WeekNo,
CASE
WHEN WeekStartDate < #startDate THEN CONVERT(DATE, '2019-01-01')
ELSE CONVERT(DATE, WeekStartDate)
END AS WeekStartDate,
CASE
WHEN WeekEndDate > #endDate THEN CONVERT(DATE, '2019-12-31')
ELSE CONVERT(DATE, WeekEndDate)
END AS WeekEndDate
FROM (
SELECT WeekNo,
DATEADD(WEEK, WeekNo - 1, #startDate) AS WeekStartDate,
DATEADD(WEEK, WeekNo - 1, #endDate) AS WeekEndDate
FROM Weeks
) a
OPTION (MAXRECURSION 1000);

Ordering dates for 4 weeks

I am writing a SQL query that pulls some information for the last 4 weeks.
I am seeing two issues with my results (see below).
First problem is that when I look back four weeks, the range should be August 10 - September 6. When I order by 'Day of the Month', the dates in September get moved to the top of the results when they should actually be at the end. So my results should start from 10 (august) and end at 6 (September).
Second problem is I'm missing a few random dates (3, 4, 13, 27).
Day of the Month Number of X
1 125
2 77
5 5
6 23
10 145
11 177
12 116
14 2
15 199
16 154
17 134
18 140
19 154
21 8
22 166
23 145
24 151
25 107
26 79
28 3
29 151
30 163
31 147
Here a general version of my query:
DECLARE #startDate datetime, #endDate datetime;
SET #startDate = dateadd(day, -28, GETDATE());
SET #endDate = dateadd(day, -1, GETDATE());
Select DATEPArt(dd, Time) AS 'Day of the Month', count(*) AS ' Number of X'
from SomeTable ST
where Time >= #startDate
AND Time < #endDate
group by DATEPArt(dd, Time)
order by 'Day of the Month'
For the first problem you can order by date to get the correct date order. I use convert to get a time-free date so that the entries group correctly.
DECLARE #StartDate datetime, #EndDate datetime;
SET #StartDate = DATEADD(day, -28, GETDATE());
SET #EndDate = DATEADD(day, -1, GETDATE());
SELECT
DATEPART(dd, Convert(date, Time)) AS 'Day of the Month', COUNT(*) AS ' Number of X'
FROM SomeTable ST
WHERE Time >= #StartDate
AND Time < #EndDate
GROUP BY Convert(date, Time)
ORDER BY Convert(date, Time)
As for the missing days, this is more complicated as the data needs to be there for the group-by to work.
One option is to create a temporary table with all the dates in, then join in the data. This will still leave a row where the join does not find any data, and can get a "zero" count.
DECLARE #StartDate datetime, #EndDate datetime;
SET #StartDate = DATEADD(day, -28, GETDATE());
SET #EndDate = DATEADD(day, -1, GETDATE());
--Create temporary table with all days between the two dates
;WITH d(d) AS
(
SELECT DATEADD(DAY, n, DATEADD(DAY, DATEDIFF(DAY, 0, #StartDate), 0))
FROM ( SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
FROM sys.all_objects ORDER BY [object_id] ) AS n
)
--Join in our query to the table temporary table
SELECT
DATEPART(dd, d.d) AS 'Day of the Month',
COUNT(Time) AS ' Number of X'
FROM d LEFT OUTER JOIN SomeTable ST ON DATEPART(dd, d.d) = DATEPART(dd, Time)
AND Time >= #StartDate
AND Time < #EndDate
GROUP BY d.d
ORDER BY d.d

need to calculate year,month and day for closing date

I have a table called dates,
Opendate | Closedate
------------+---------------
2015-07-09 | 2016-08-10
I am expecting the output like,
opendate | closedate | diff
------------+---------------+----------------------
2015-07-09 | 2016-08-10 | 1year 1month 1day
2015-07-09 | 2016-03-01 | 8 months 20 days
2015-07-09 | 2015-07-11 | 2 days
But when I run this query:
SELECT opendate,
closedate,
Datediff(year, opendate, closedate) AS years,
Datediff(month, opendate, closedate) AS months,
Datediff(day, opendate, closedate) AS days
FROM dates
It is giving me an output like,
opendate | closedate | years | months | days
------------+---------------+-------+--------+---------
2015-07-09 | 2016-08-10 | 1 | 13 | 397
How can we calculate 1 year 1 month and 1 day
You can use Stacked CTE to find one by one the next year, month and date.
Explanation
Query Below first finds out the DATEDIFF Years of opendate and closedate and checks if the resulting date is greater than closedate. if it is, the actual year difference is DATEDIFF of Y -1. use this new date and fetch the DATEDIFF of months using the same logic and then get the difference in days.
Online Example
Query
WITH D(Opendate,Closedate)AS
(
SELECT CAST('2015-07-09' AS DATE),CAST('2016-08-10' AS DATE)
UNION ALL
SELECT CAST('2015-07-09' AS DATE),CAST('2016-03-01' AS DATE)
UNION ALL
SELECT CAST('2015-07-09' AS DATE),CAST('2015-07-11' AS DATE)
),Y AS
(
SELECT Opendate,Closedate,
CASE
WHEN DATEADD(YEAR,DATEDIFF(YEAR,Opendate,Closedate),Opendate) > Closedate
THEN DATEDIFF(YEAR,Opendate,Closedate) - 1
ELSE DATEDIFF(YEAR,Opendate,Closedate)
END Years
FROM D
), YDate as
(
SELECT Opendate,Closedate,Years,DATEADD(YEAR,Years,Opendate) as Newopendate
FROM Y
),M AS
(
SELECT Opendate,Closedate,Years,Newopendate,
CASE WHEN DATEADD(MONTH,DATEDIFF(MONTH,Newopendate,Closedate),Newopendate) > Closedate
THEN DATEDIFF(MONTH,Newopendate,Closedate) - 1
ELSE DATEDIFF(MONTH,Newopendate,Closedate)
END Months
FROM YDate
)
SELECT Opendate,Closedate,Years,Months,DATEDIFF(Day,DATEADD(MONTH,Months,Newopendate),Closedate) as days
FROM M
Result
Opendate Closedate Years Months days
09-07-2015 00:00 10-08-2016 00:00 1 1 1
09-07-2015 00:00 01-03-2016 00:00 0 7 21
09-07-2015 00:00 11-07-2015 00:00 0 0 2
SELECT opendate,
closedate,
( ( Datediff(year, opendate, closedate) + 'years' )+
(( Datediff(month, opendate, closedate) -
12 * Datediff(year, opendate, closedate)) + 'months') +
( Datediff(day, opendate, closedate) -
( Datediff(year, opendate, closedate) * 365 -
(Datediff(month, opendate, closedate) * 12) )) + 'days'
FROM dates
The logic is you concatenate the years and then deduct the no of months of a year. Similarly deduct for days as well
Create one function as Below
CREATE FUNCTION dbo.GetYearMonthDays
(
#FromDate DATETIME
)
RETURNS NVARCHAR(100)
AS
BEGIN
DECLARE #date datetime, #tmpdate datetime, #years int, #months int, #days int
SELECT #date =#FromDate
SELECT #tmpdate = #date
SELECT #years = DATEDIFF(yy, #tmpdate, GETDATE()) - CASE WHEN (MONTH(#date) > MONTH(GETDATE())) OR (MONTH(#date) = MONTH(GETDATE()) AND DAY(#date) > DAY(GETDATE())) THEN 1 ELSE 0 END
SELECT #tmpdate = DATEADD(yy, #years, #tmpdate)
SELECT #months = DATEDIFF(m, #tmpdate, GETDATE()) - CASE WHEN DAY(#date) > DAY(GETDATE()) THEN 1 ELSE 0 END
SELECT #tmpdate = DATEADD(m, #months, #tmpdate)
SELECT #days = DATEDIFF(d, #tmpdate, GETDATE())
RETURN CONVERT(varchar(10), #years) +' Years ' + CONVERT(varchar(10), #months) + ' Month ' + CONVERT(varchar(10), #days) + ' Days'
END
GO
And use is as below
SELECT opendate,
closedate,dbo.GetYearMonthDays(closedate)
FROM dates
This will give you what you wants.

Calculate exact date difference in years using SQL

I receive reports in which the data is ETL to the DB automatically. I extract and transform some of that data to load it somewhere else. One thing I need to do is a DATEDIFF but the year needs to be exact (i.e., 4.6 years instead of rounding up to five years.
The following is my script:
select *, DATEDIFF (yy, Begin_date, GETDATE()) AS 'Age in Years'
from Report_Stage;
The 'Age_In_Years' column is being rounded. How do I get the exact date in years?
All datediff() does is compute the number of period boundaries crossed between two dates. For instance
datediff(yy,'31 Dec 2013','1 Jan 2014')
returns 1.
You'll get a more accurate result if you compute the difference between the two dates in days and divide by the mean length of a calendar year in days over a 400 year span (365.2425):
datediff(day,{start-date},{end-date},) / 365.2425
For instance,
select datediff(day,'1 Jan 2000' ,'18 April 2014') / 365.2425
return 14.29461248 — just round it to the desired precision.
Have you tried getting the difference in months instead and then calculating the years that way? For example 30 months / 12 would be 2.5 years.
Edit: This SQL query contains several approaches to calculate the date difference:
SELECT CONVERT(date, GetDate() - 912) AS calcDate
,DATEDIFF(DAY, GetDate() - 912, GetDate()) diffDays
,DATEDIFF(DAY, GetDate() - 912, GetDate()) / 365.0 diffDaysCalc
,DATEDIFF(MONTH, GetDate() - 912, GetDate()) diffMonths
,DATEDIFF(MONTH, GetDate() - 912, GetDate()) / 12.0 diffMonthsCalc
,DATEDIFF(YEAR, GetDate() - 912, GetDate()) diffYears
I think that division by 365.2425 is not a good way to do this. No division can to this completely accurately (using 365.25 also has issues).
I know the following script calculates an accurate date difference (though might not be the most speedy way):
declare #d1 datetime ,#d2 datetime
--set your dates eg:
select #d1 = '1901-03-02'
select #d2 = '2016-03-01'
select DATEDIFF(yy, #d1, #d2) -
CASE WHEN MONTH(#d2) < MONTH(#d1) THEN 1
WHEN MONTH(#d2) > MONTH(#d1) THEN 0
WHEN DAY(#d2) < DAY(#d1) THEN 1
ELSE 0 END
-- = 114 years
For comparison:
select datediff(day,#d1 ,#d2) / 365.2425
-- = 115 years => wrong!
You might be able to calculate small ranges with division, but why take a chance??
The following script can help to test yeardiff functions (just swap cast(datediff(day,#d1,#d2) / 365.2425 as int) to whatever the function is):
declare #d1 datetime set #d1 = '1900-01-01'
while(#d1 < '2016-01-01')
begin
declare #d2 datetime set #d2 = '2016-04-01'
while(#d2 >= '1900-01-01')
begin
if (#d1 <= #d2 and dateadd(YEAR, cast(datediff(day,#d1,#d2) / 365.2425 as int) , #d1) > #d2)
begin
select 'not a year!!', #d1, #d2, cast(datediff(day,#d1,#d2) / 365.2425 as int)
end
set #d2 = dateadd(day,-1,#d2)
end
set #d1 = dateadd(day,1,#d1)
end
You want the years difference, but reduced by 1 when the "day of the year" of the future date is less than that of the past date. So like this:
SELECT *
,DATEDIFF(YEAR, [Begin_date], [End_Date])
+ CASE WHEN CAST(DATENAME(DAYOFYEAR, [End_Date]) AS INT)
>= CAST(DATENAME(DAYOFYEAR, [Begin_date]) AS INT)
THEN 0 ELSE -1 END
AS 'Age in Years'
from [myTable];
For me I calculate the difference in days
Declare #startDate datetime
Declare #endDate datetime
Declare #diff int
select #diff=datediff(day,#startDate,#endDate)
if (#diff>=365) then select '1Year'
if (#diff>=730) then select '2Years'
-----etc
I have found a better solution. This makes the assumption that the first date is less than or equal to the second date.
declare #dateTable table (date1 datetime, date2 datetime)
insert into #dateTable
select '2017-12-31', '2018-01-02' union
select '2017-01-03', '2018-01-02' union
select '2017-01-02', '2018-01-02' union
select '2017-01-01', '2018-01-02' union
select '2016-12-01', '2018-01-02' union
select '2016-01-03', '2018-01-02' union
select '2016-01-02', '2018-01-02' union
select '2016-01-01', '2018-01-02'
select date1, date2,
case when ((DATEPART(year, date1) < DATEPART(year, date2)) and
((DATEPART(month, date1) <= DATEPART(month, date2)) and
(DATEPART(day, date1) <= DATEPART(day, date2)) ))
then DATEDIFF(year, date1, date2)
when (DATEPART(year, date1) < DATEPART(year, date2))
then DATEDIFF(year, date1, date2) - 1
when (DATEPART(year, date1) = DATEPART(year, date2))
then 0
end [YearsOfService]
from #dateTable
date1 date2 YearsOfService
----------------------- ----------------------- --------------
2016-01-01 00:00:00.000 2018-01-02 00:00:00.000 2
2016-01-02 00:00:00.000 2018-01-02 00:00:00.000 2
2016-01-03 00:00:00.000 2018-01-02 00:00:00.000 1
2016-12-01 00:00:00.000 2018-01-02 00:00:00.000 1
2017-01-01 00:00:00.000 2018-01-02 00:00:00.000 1
2017-01-02 00:00:00.000 2018-01-02 00:00:00.000 1
2017-01-03 00:00:00.000 2018-01-02 00:00:00.000 0
2017-12-31 00:00:00.000 2018-01-02 00:00:00.000 0