Converting INT to DATE then using GETDATE on conversion? - sql

I am trying to convert the results from an INT column to DATE so the GETDATE function will be compatible with this column. The date is currently in the format yyyymmdd
This is what I have so far based on what I could find but I am sure it is completely wrong
...AND (dbo.V_HEAD.LF_DATE CONVERT(DATE,(CONVERT(INT, LF_DATE)) >= GETDATE-28)
AND (dbo.V_HEAD.LF_DATE CONVERT(DATE,(CONVERT(INT, LF_DATE)) <= GETDATE)...
I want the results qualified on LF_DATE for the last 28 days too
The rest of the script runs correctly.
Where am I going wrong and how can I correct it?

Update
Following your comments, I've created some sample data to test my answer:
Create and populate sample data (Please save us this step in your future questions)
DECLARE #T as TABLE
(
Id int,
ActualDate Date,
LF_Date int
)
INSERT INTO #T (Id, ActualDate) VALUES
(10, DATEADD(DAY, -5, GETDATE())),
(9, DATEADD(DAY, -10, GETDATE())),
(8, DATEADD(DAY, -15, GETDATE())),
(7, DATEADD(DAY, -20, GETDATE())),
(6, DATEADD(DAY, -25, GETDATE())),
(5, DATEADD(DAY, -30, GETDATE())),
(4, DATEADD(DAY, -35, GETDATE())),
(3, DATEADD(DAY, -40, GETDATE())),
(2, DATEADD(DAY, -45, GETDATE())),
(1, DATEADD(DAY, -50, GETDATE()))
UPDATE #T
SET LF_Date = YEAR(ActualDate) * 10000 + MONTH(ActualDate) * 100 + DAY(ActualDate)
Test sample data:
SELECT *
FROM #T
Results:
Id ActualDate LF_Date
----------- ---------- -----------
10 2016-08-09 20160809
9 2016-08-04 20160804
8 2016-07-30 20160730
7 2016-07-25 20160725
6 2016-07-20 20160720
5 2016-07-15 20160715
4 2016-07-10 20160710
3 2016-07-05 20160705
2 2016-06-30 20160630
1 2016-06-25 20160625
As you can see, the sample table's LF_Date column is an int that keeps the date as yyyyMMdd, just like in the question.
The query:
DECLARE #DateAsInt int,
#Date date = GETDATE();
SELECT #DateAsInt = YEAR(#Date) * 10000 + MONTH(#Date) * 100 + DAY(#Date);
SELECT *
FROM #T
WHERE LF_DATE >= #DateAsInt - 28
AND LF_DATE <= #DateAsInt
Results:
Id ActualDate LF_Date
----------- ---------- -----------
10 2016-08-09 20160809
9 2016-08-04 20160804
Conclusion:
as far as the sample data goes, the answer is fine. You need to test your data to see what's stopping you from getting the results from the previous month, but I seriously doubt that it's my suggestion.
First version
Assuming your Sql server version is 2012 or higher, you can use some math and the DATEFROMPARTS built in function:
DECLARE #IntDate int = 20160322
SELECT DATEFROMPARTS (
(#IntDate - (#IntDate % 10000)) / 10000,
(#IntDate % 1000) / 100,
#IntDate % 100
) As [Date]
Results:
Date
2016-03-22
However, It will be simpler and probably have a better performance to convert the date to int:
DECLARE #Date date = '2016-03-22'
SELECT YEAR(#Date) * 10000 +
MONTH(#Date) * 100 +
DAY(#Date) As [Int]
Results:
Int
20160322
To put that in context of your question - calculate the int value of the current date before your query:
DECLARE #DateAsInt int,
#Date date = GETDATE();
SELECT #DateAsInt = YEAR(#Date) * 10000 + MONTH(#Date) * 100 + DAY(#Date);
And then, in your where clause you simply write this:
...
AND LF_DATE >= #DateAsInt - 28
AND LF_DATE <= #DateAsInt
...
In any case, you will be better off if you could change your table structure and replace that int column with a date column.
Read Aaron Bertrand's Bad habits to kick : choosing the wrong data type.

Perhaps this may help
Select DateAdd(DD,-28,cast(cast(20160809 as varchar(8)) as date))
Returns 2016-07-12
However, since your data is an int, I think it would be more efficient to convert the desired date range into an int rather than performing row level calculations
Declare #DateR1 int = Format(DateAdd(DD,-28,GetDate()),'yyyyMMdd')
Declare #DateR2 int = Format(GetDate(),'yyyyMMdd')
Select DateR1=#DateR1,DateR2=#DateR2
Returns
DateR1 DateR2
20160712 20160809

#Zohar Peled, I think I have cracked it! It is subtracting 28 as an int and not days.
The problem is 20160809 - 28 = 20160781 which is no good
The desired results would be
SELECT *
FROM #T
WHERE LF_DATE >= #DateAsInt - 28 (DAYS)
AND LF_DATE <= #DateAsInt
Id ActualDate LF_Date
10 2016-08-09 20160809
9 2016-08-04 20160804
8 2016-07-30 20160730
7 2016-07-25 20160725
6 2016-07-20 20160720
5 2016-07-15 20160715
As 20160809 - 28 DAYS would include dates from 20160712
The way around this was to subtract 97 instead of 28.
This this is not very clean, there must be a better way...

Related

Auto pickup dates from SQL Server - T-SQL

I am looking for some T-SQL code that should pick the date which is "One Year back from current date (at the same time last Sunday in the month of January)".
For example:
Current day expected result
2017-02-05 2016-01-31
2017-01-05 2015-01-25
2018-02-19 2017-01-29
2018-01-19 2016-01-31
2019-02-28 2018-01-28
Please note: The year starts from last Sunday in January
I have some T-SQL code which is being used in SQL Server 2014:
select
convert(varchar(10), DATEADD(day, DATEDIFF(day, '19000107', DATEADD(month, DATEDIFF(MONTH, 0, CONVERT(date, CONVERT(VARCHAR(4), (CASE WHEN MONTH(GetDate()) = 1 THEN CONVERT(VARCHAR(4), GetDate(), 112) - 1 ELSE CONVERT(VARCHAR(4), GetDate(), 112) END), 112) + '0101')), 30)) / 7 * 7, '19000107'), 120)
The above code picks the date for current year's (last Sunday in January month). But I want T-SQL code to pick last year's (last Sunday's date in January month) date.
In detail - I want T-SQL code to produce expected result from below table
Current day T-SQL code answer expected result
2017-02-05 2017-01-29 2016-01-31
2017-01-05 2016-01-31 2015-01-25
2018-02-19 2018-01-28 2017-01-29
2018-01-19 2017-01-29 2016-01-31
2019-02-28 2019-01-27 2018-01-28
Any help please.
The best thing for this question is a numbers and date table. This answer shows you how to create one. Such a table is very handsome in many situations...
If I understand this correctly, you want the last Sunday in January of the previous year in all cases? Try this:
DECLARE #dummy TABLE(ID INT IDENTITY,YourDate DATE);
INSERT INTO #dummy VALUES
('2017-02-05'),('2017-01-05'),('2018-02-19'),('2018-01-19'),('2019-02-28');
WITH Years AS
(
SELECT * FROM (VALUES(2010),(2011),(2012),(2013),(2014),(2015),(2016),(2017),(2018),(2019),(2020)) AS t(Yr)
)
,LastSundays AS
(
SELECT Yr AS TheYear
,DATEADD(DAY,(DATEPART(WEEKDAY,LastOfJanuary) % 7)*(-1),LastOfJanuary) AS LastSundayOfJanuary
FROM Years
CROSS APPLY(SELECT CAST(CAST(Yr AS VARCHAR(4)) + '0131' AS DATE)) AS t(LastOfJanuary)
)
SELECT *
FROM #dummy AS d
INNER JOIN LastSundays AS ls ON YEAR(DATEADD(YEAR,-1,d.YourDate))=ls.TheYear;
The result (I do not understand row 2 and 4 completely...)
ID YourDate TheYear LastSundayOfJanuary
1 2017-02-05 2016 2016-01-31
2 2017-01-05 2016 2016-01-31 <--Your sample data is different...
3 2018-02-19 2017 2017-01-29
4 2018-01-19 2017 2017-01-29 <--Your sample data is different...
5 2019-02-28 2018 2018-01-28
Hint You might need to introduce ##DATEFIRST into your calculations...
Here is a way to do it without a date table (which is still a good idea BTW). Tested on all your inputs and it delivers the correct output each time. Obviously you would refactor this a bit as it's longwinded, just to show each step.
/* The input date. */
DECLARE
#input DATE = '2019-02-28';
/* The input date less one year. */
DECLARE
#date_minus_one_year DATE = DATEADD(yy,-1,#input);
/* The year part of the input date less one year. */
DECLARE
#year_date_part INT = DATEPART(yy,#date_minus_one_year);
/* 31 Jan of the previous year. */
DECLARE
#prev_year_jan_eom DATE = CAST(CAST(#year_date_part AS VARCHAR(4))+'-01-31' AS DATE);
/* What day of the week is 31 Jan of the previous year? */
DECLARE
#previous_eom_dw_part INT = DATEPART(dw,#prev_year_jan_eom);
/* Offest 31 Jan to the previous Sunday, won't change if the 31st is itself a Sunday. */
DECLARE
#output DATE = DATEADD(dd,1 - #previous_eom_dw_part,#prev_year_jan_eom);
/* Input and output */
SELECT
#input input
,#output [output];
I didn't think of a way to do it without the conditional in a case. It also uses the trick of casting a numeric year value to a January 1st date.
select case
when
datepart(dayofyear, dt) >
31 - datepart(weekday, dateadd(day, 30, cast(year(dt) as varchar(4))))
then
dateadd(day,
31 - datepart(weekday, dateadd(day, 30, cast(year(dt) as varchar(4)))),
cast(year(dt) as varchar(4))
)
else
dateadd(day,
31 - datepart(weekday, dateadd(day, 30, cast(year(dt) - 1 as varchar(4)))),
cast(year(dt) - 1 as varchar(4))
)
end
from (values
('20100201'), ('20110301'), ('20120401'),
('20130501'), ('20140601'), ('20150701'),
('20160801'), ('20170901'), ('20181001')
) t(dt)
Just for fun (untested)
select
dateadd(week,
-52 * ceil(sign(datediff(day, dt, hs)) + 0.5),
js
)
from
(select <date> dt) as t
cross apply
(
select 31 - datepart(weekday,
datefromparts(year(dt), 1, 31) as js
) t2;
SELECT
convert(varchar(10), DATEADD(day, DATEDIFF(day, '19000107', DATEADD(month, DATEDIFF(MONTH, 0, CONVERT(date, CONVERT(VARCHAR(4), (CASE WHEN MONTH(DATEADD(year,-1,GetDate())) = 1 THEN CONVERT(VARCHAR(4), DATEADD(year,-1,GetDate()), 112) - 1 ELSE CONVERT(VARCHAR(4), DATEADD(year,-1,GetDate()), 112) END), 112) + '0101')), 30)) / 7 * 7, '19000107'), 120)

At any given time, how many items were not processed?

Consider a table representing a queue, with a datetime timestamp for when an item has been added, and one for when it is completed.
The question:
How can I efficiently query for any given time, how many items were in queue, ie. added but not completed.
A table sample example:
id value added completed
6 1 2016-01-01 00:00:12.345 2016-01-01 00:01:12.345
7 500 2016-01-01 01:12:12.345 2016-01-01 01:15:12.345
8 1 2016-01-01 01:12:12.345 2016-01-01 02:16:12.345
9 2 2016-01-01 01:33:12.345 NULL
10 2 2016-01-01 01:33:12.345 NULL
11 2 2016-01-01 01:33:12.345 NULL
Items can be added at any time, but it takes time for them to be completed.
In the example above, 9, 10 and 11 are under process, so I can easily query to find that 3 items are in queue right now. But how do I query to find for example how many items were in queue and not completed at any given past time?
I am looking for a result that looks something like this:
date time count sum value
2016-01-01 00:00:00.000 1 1
2016-01-01 00:12:00.000 2 501
2016-01-01 00:13:00.000 2 501
2016-01-01 00:14:00.000 2 501
2016-01-01 00:15:00.000 1 1
2016-01-01 00:33:00.000 3 6
My goal is to find the times with max number of items in queue. From here I would be able to say that the queued item size was highest at 00:33 and that the queued value size was the highest 00:12-00:14.
What I've tried: I have experimented with WITH like suggested in this answer. It works fine for only one date, but when I use both hs.added>= DATEADD(... and hs.completed >= DATEADD( criteria, the execution that was 0 seconds now seen to never complete. I am not completely grasping the execution process here.
This large table is in production, and I do not want to keep a query executing for too long.
Edit: stats:
COLUMN_NAME DATA_TYPE CHARACTER_MAXIMUM_LENGTH IS_NULLABLE
ID int NULL NO
added datetime NULL NO
completed datetime NULL YES
value int NULL NO
CONSTRAINT_NAME
PK_Queue
name type_desc is_unique is_primary_key
PK_Queue CLUSTERED 1 1
IX_Queue_completed NONCLUSTERED 0 0
IX_Queue_added NONCLUSTERED 0 0
rows data
6 000 000 15 000 000 KB
The basic query looks like this for a given time:
select count(*), sum(q.value)
from queue q
where #datetime >= q.added and
(#datetime < q.completed or q.completed is null);
For all times, you can just put them together in a subquery and join them in:
select dt.dt, count(q.id), sum(q.value)
from (select q.added as dt from queue q union select q.completed from queue q
) dt left join
queue q
on dt.dt >= q.added and (dt.dt < q.completed or q.completed is null)
group by dt.dt
order by dt.dt;
To get the maximum value, add top 1 and order by count(q.id) desc.
For your consideration:
I use a UDF to generate dynamic date ranges (listed below).
Just a quick note, on ID 8, I'm assuming you had a typo on the complete date (1:16 vs 2:16).
Declare #Table table (id int, value int,Added datetime,complete datetime)
Insert into #Table values
(6, 1, '2016-01-01 00:00:12.345','2016-01-01 00:01:12.345'),
(7, 500,'2016-01-01 01:12:12.345','2016-01-01 01:15:12.345'),
(8, 1 ,'2016-01-01 01:12:12.345','2016-01-01 01:16:12.345'),
(9, 2 ,'2016-01-01 01:33:12.345',NULL),
(10, 2 ,'2016-01-01 01:33:12.345',NULL),
(11, 2 ,'2016-01-01 01:33:12.345',NULL)
Declare #DateR1 DateTime = '2016-01-01 00:00'
Declare #DateR2 DateTime = '2016-01-01 01:35'
Declare #DatePart varchar(25) = 'MI'
Declare #DateIncr int = 1
Select KeyDate
,Count = sum(isnull(Sign(B.Value),0))
,Value = isnull(sum(Value),0)
From (Select KeyDate = RetVal From [dbo].[udf-Create-Range-Date](#DateR1,#DateR2,#DatePart,#DateIncr)) A
Left Join #Table B
on KeyDate between added and IsNull(complete,#DateR2)
Group By KeyDate
Having sum(value)>0 -- Optional for zero supression
Order By KeyDate
Returns
KeyDate Count Value
2016-01-01 00:01:00.000 1 1
2016-01-01 01:13:00.000 2 501
2016-01-01 01:14:00.000 2 501
2016-01-01 01:15:00.000 2 501
2016-01-01 01:16:00.000 1 1
2016-01-01 01:34:00.000 3 6
2016-01-01 01:35:00.000 3 6
The UDF - there are many options out there or you could even use a Date or Tally Table.
CREATE FUNCTION [dbo].[udf-Create-Range-Date] (#DateFrom datetime,#DateTo datetime,#DatePart varchar(10),#Incr int)
Returns
#ReturnVal Table (RetVal datetime)
As
Begin
With DateTable As (
Select DateFrom = #DateFrom
Union All
Select Case #DatePart
When 'YY' then DateAdd(YY, #Incr, df.dateFrom)
When 'QQ' then DateAdd(QQ, #Incr, df.dateFrom)
When 'MM' then DateAdd(MM, #Incr, df.dateFrom)
When 'WK' then DateAdd(WK, #Incr, df.dateFrom)
When 'DD' then DateAdd(DD, #Incr, df.dateFrom)
When 'HH' then DateAdd(HH, #Incr, df.dateFrom)
When 'MI' then DateAdd(MI, #Incr, df.dateFrom)
When 'SS' then DateAdd(SS, #Incr, df.dateFrom)
End
From DateTable DF
Where DF.DateFrom < #DateTo
)
Insert into #ReturnVal(RetVal) Select DateFrom From DateTable option (maxrecursion 32767)
Return
End
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','YY',1)
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','DD',1)
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-31','MI',15)
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-02','SS',1)

SQL Server 2012: GROUP BY seconds, minutes, hours

I am trying to group records in SQL Server 2012 by DateTime. I found an example on stackoverflow that does partially what I want, but the problem is that it does not group correct when it exceeds the range. If, for example, I group on minutes in blocks of 30 minutes the result is correct (see query and result below). But if I group on blocks of 120 minutes, I got the exact same result. It keeps grouping at its maximum of 60 minutes in an hour (result below).
The problem is that the grouping can not take it's parent DateTime element (seconds to minutes, minutes to hours, ... , even seconds to hours,...). It is kinda logic cause I only check at minutes, but I would like to see it pass hours also. Maybe with a DATEADD(), but I don't manage to get it working.
Any ideas??
A (small) example to show what I mean:
Query:
DECLARE #TimeInterval int, #StartTime DateTime, #EndTime Datetime
SET #TimeInterval = 30
SET #StartTime='2015-01-01T08:00:00Z'
SET #EndTime = '2015-01-05T10:00:00Z'
declare #T table
(
Value datetime
);
insert into #T values ('2015-01-01T08:00:00');
insert into #T values ('2015-01-01T08:03:00');
insert into #T values ('2015-01-01T08:06:00');
insert into #T values ('2015-01-01T08:14:00');
insert into #T values ('2015-01-01T09:06:00');
insert into #T values ('2015-01-01T09:07:00');
insert into #T values ('2015-01-01T09:08:00');
insert into #T values ('2015-01-01T11:09:00');
insert into #T values ('2015-01-01T12:10:00');
insert into #T values ('2015-01-01T13:11:00');
insert into #T values ('2015-01-02T08:08:00');
insert into #T values ('2015-01-02T08:09:00');
insert into #T values ('2015-01-03T08:10:00');
insert into #T values ('2015-01-04T08:11:00');
SELECT DATEADD(MINUTE, #TimeInterval, Convert(Datetime,CONCAT(DATEPART(YEAR, Value),'-', DATEPART(MONTH, Value),
'-', DATEPART(DAY, Value),' ', DATEPART(HOUR, Value),':', ((DATEPART(MINUTE, Value) / #TimeInterval) * #TimeInterval),':00'),120)) as Period,
ISNULL(COUNT(*), 0) AS NumberOfVisitors
FROM #T
WHERE Value >= #StartTime AND Value < #EndTime
GROUP BY Convert(Datetime,CONCAT(DATEPART(YEAR, Value),'-', DATEPART(MONTH, Value), '-', DATEPART(DAY, Value),' ',
DATEPART(HOUR, Value),':',((DATEPART(MINUTE, Value) / #TimeInterval) * #TimeInterval),':00'),120)
ORDER BY Period
Result for 30 min
2015-01-01 08:30:00.000 | 4
2015-01-01 09:30:00.000 | 3
2015-01-01 11:30:00.000 | 1
2015-01-01 12:30:00.000 | 1
2015-01-01 13:30:00.000 | 1
2015-01-02 08:30:00.000 | 2
2015-01-03 08:30:00.000 | 1
2015-01-04 08:30:00.000 | 1
Result for 60 min
2015-01-01 08:30:00.000 | 4
2015-01-01 09:30:00.000 | 3
2015-01-01 11:30:00.000 | 1
2015-01-01 12:30:00.000 | 1
2015-01-01 13:30:00.000 | 1
2015-01-02 08:30:00.000 | 2
2015-01-03 08:30:00.000 | 1
2015-01-04 08:30:00.000 | 1
Thanks in advance!
You don't want datepart() for this purpose. You want a minutes count. One way is to use datediff():
SELECT datediff(minute, #StartTime, value) / #TimeInterval as minutes,
COUNT(*) AS NumberOfVisitors
FROM #T
WHERE Value >= #StartTime AND Value < #EndTime
GROUP BY datediff(minute, #StartTime, value) / #TimeInterval
ORDER BY minutes ;
SQL Server does integer division, so you don't have to worry about remainders in this case. Also, COUNT(*) cannot return NULL, so neither ISNULL() nor COALESCE() is appropriate.
Or, to get a date/time value:
SELECT dateadd(day,
datediff(minute, #StartTime, value) / #TimeInterval,
#StartTime) as period,
COUNT(*) AS NumberOfVisitors
FROM #T
WHERE Value >= #StartTime AND Value < #EndTime
GROUP BY datediff(minute, #StartTime, value) / #TimeInterval
ORDER BY period ;

How to set a specific time interval for different work shifts to retrieve data

I have two working shifts: 8:00:00 to 16:30:00 and 20:00:00 to 06:00:00.
I want to create a stored procedure that will retrieve data from an SQL table when I pass the date
this are my tables
Table1 Emp
ID DateTime EmpID
47 2014-12-05 08:00:00 1111
47 2014-12-05 08:25:00 1235
47 2014-12-05 23:55:00 4569
47 2014-12-06 00:00:00 4563
47 2014-12-06 02:00:00 7412
59 2014-12-06 04:00:00 8523
59 2014-12-05 10:30:00 5632
Table2 Product
ID DateTime ProductMade
47 2014-12-05 11:00:00 Milk
47 2014-12-05 08:00:00 Juice
47 2014-12-06 00:00:00 Bread
47 2014-12-06 06:00:00 Cakes
query for shift 2 18:00 to 06:00
SELECT *
FROM Table 1 as T1
INNER JOIN Table_Product as Prod ON t1.ID=Prod.ID
WHERE T1.DateTime BETWEEN DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()-8), 0) + '18:00'
AND DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()-7), 0) + '06:00'
so this will get all the records that has the same ID matching
then i have to do another query for the first shift.
between 08:00 to 16:30
SELECT *
FROM Table 1 AS T1
INNER JOIN
Table_Product AS Prod ON t1.ID=Prod.ID
WHERE DATEDIFF(day, CONVERT(VARCHAR(10), GETDATE(),110), CONVERT(VARCHAR(10), T1.DateTime,110))=-1 AND DATEPART(HOUR,T1.DateTime) BETWEEN '07' AND '16'
How do i make this into one stored procdure and elminate having two queries.
Try this if you want it for a specific shift. Then you have to specify #Shift
Declare #Shift char(1),
#days int
Set #Shift = 'A' -- will only get information for SHIFT A. Change to B if you want the rest
Set #days = 1
Select *
from Table1 t
where t.DateTime between
case when #Shift = 'A' then DateAdd(hour, 8, Convert(date, GetDate() - #days))
else DateAdd(hour, 20, Convert(date, GetDate() - #days)) end
and
case when #Shift = 'A' then DateAdd(hour, 16, Convert(date, GetDate() - #days))
else DateAdd(hour, 30, Convert(date, GetDate() - #days)) end
Specify the Shift and a Date, and it should work.
You can always do something like this as well. This you only have to specify the number of days in the past, and it will retrieve the information and specify the Shift in the first Column
DECLARE #days int
SET #days = 1
Select case when DATEPART(hour, t.DateTime) between 8 and 16 then 'A' else 'B' end AS Shift, *
from Table1 t
where t.DateTime between DateAdd(hour, 8, Convert(date, GetDate() - #days))
and DateAdd(hour, 30, Convert(date, GetDate() - #days))
ORDER BY 1, t.DateTime
It seems that you have two shifts per day and the day shift begins before the night shift. So, let's enumerate the shifts and let you choose the one(s) you want that way:
select t.*
from (select t.*,
row_number() over (partition by cast(sp.datetime as date)
order by sp.datetime
) as shiftnumber
from table t
) t
where DATEDIFF(day, CAST(GETDATE() as DATE), CAST(SP.DateTime as DATE)) = -1 and
shiftnumber = 1;
Note that I also changed the date arithmetic. The conversion to dates uses the built-in DATE type. Converting a date to a string and back to a date is inelegant.

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