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

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)

Related

Multiple counts and merge columns

I current have a query that grabs the number of parts made per hour between two dates:
DECLARE #StartDate datetime
DECLARE #EndDate datetime
SET #StartDate = '10/10/2018'
SET #EndDate = '11/11/2018'
SELECT
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111) AS ForDate,
DATEPART(HOUR, presstimes) AS OnHour,
COUNT(*) AS Totals
FROM
partmasterlist
WHERE
((presstimes >= #StartDate AND presstimes < dateAdd(d, 1, #EndDate))
AND (((presstimes IS NOT NULL))))
GROUP BY
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111),
DATEPART(HOUR, presstimes)
ORDER BY
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111) ASC;
Output:
Date Hour QTY
---------------------
2018/11/06 11 16
2018/11/06 12 20
2018/11/06 13 29
2018/11/06 14 26
Now I need to add another qty column to count where "trimmingtimes" is set.
I can't figure out how to full join the date and hour columns (e.g. presstimes might have 20qty for Hour 2, but trimmingtimes is NULL for Hour 2);
Input:
ID presstimes trimmingtimes
-----------------------------------------------------------------
1 2018-10-10 01:15:23.000 2018-10-10 01:15:23.000
2 2018-10-10 01:15:23.000 NULL
3 2018-10-10 02:15:23.000 NULL
4 NULL 2018-10-10 03:15:23.000
Output:
Date hour Press QTY T QTY
------------------------------------
10/10/18 1 2 1
10/10/18 2 1 0
10/10/18 3 0 1
I suspect you want something like this:
select convert(date, v.dt) as date,
datepart(hour, v.dt) as hour,
sum(ispress) as num_press,
sum(istrim) as num_trim
from partmasterlist pml cross apply
(values (pml.presstime, 1, 0), (pml.trimmingtime, 0, 1)
) v(dt, ispress, istrim)
group by convert(date, v.dt), datepart(hour, v.dt)
order by convert(date, v.dt), datepart(hour, v.dt);
You can add a where clause for a particular range.

SQL how to count census points occurring between date records

I’m using MS-SQL-2008 R2 trying to write a script that calculates the Number of Hospital Beds occupied on any given day, at 2 census points: midnight, and 09:00.
I’m working from a data set of patient Ward Stays. Basically, each row in the table is a record of an individual patient's stay on a single ward, and records the date/time the patient is admitted onto the ward, and the date/time the patient leaves the ward.
A sample of this table is below:
Ward_Stay_Primary_Key | Ward_Start_Date_Time | Ward_End_Date_Time
1 | 2017-09-03 15:04:00.000 | 2017-09-27 16:55:00.000
2 | 2017-09-04 18:08:00.000 | 2017-09-06 18:00:00.000
3 | 2017-09-04 13:00:00.000 | 2017-09-04 22:00:00.000
4 | 2017-09-04 20:54:00.000 | 2017-09-08 14:30:00.000
5 | 2017-09-04 20:52:00.000 | 2017-09-13 11:50:00.000
6 | 2017-09-05 13:32:00.000 | 2017-09-11 14:49:00.000
7 | 2017-09-05 13:17:00.000 | 2017-09-12 21:00:00.000
8 | 2017-09-05 23:11:00.000 | 2017-09-06 17:38:00.000
9 | 2017-09-05 11:35:00.000 | 2017-09-14 16:12:00.000
10 | 2017-09-05 14:05:00.000 | 2017-09-11 16:30:00.000
The key thing to note here is that a patient’s Ward Stay can span any length of time, from a few hours to many days.
The following code enables me to calculate the number of beds at both census points for any given day, by specifying the date in the case statement:
SELECT
'05/09/2017' [Date]
,SUM(case when Ward_Start_Date_Time <= '05/09/2017 00:00:00.000' AND (Ward_End_Date_Time >= '05/09/2017 00:00:00.000' OR Ward_End_Date_Time IS NULL)then 1 else 0 end)[No. Beds Occupied at 00:00]
,SUM(case when Ward_Start_Date_Time <= '05/09/2017 09:00:00.000' AND (Ward_End_Date_Time >= '05/09/2017 09:00:00.000' OR Ward_End_Date_Time IS NULL)then 1 else 0 end)[No. Beds Occupied at 09:00]
FROM
WardStaysTable
And, based on the sample 10 records above, generates this output:
Date | No. Beds Occupied at 00:00 | No. Beds Occupied at 09:00
05/09/2017 | 4 | 4
To perform this for any number of days is obviously onerous, so what I’m looking to create is a query where I can specify a start/end date parameter (e.g. 1st-5th Sept), and for the query to then evaluate the Ward_Start_Date_Time and Ward_End_Date_Time variables for each record, and – grouping by the dates defined in the date parameter – count each time the 00:00:00.000 and 09:00:00.000 census points fall between these 2 variables, to give an output something along these lines (based on the above 10 records):
Date | No. Beds Occupied at 00:00 | No. Beds Occupied at 09:00
01/09/2017 | 0 | 0
02/09/2017 | 0 | 0
03/09/2017 | 0 | 0
04/09/2017 | 1 | 1
05/09/2017 | 4 | 4
I’ve approached this (perhaps naively) thinking that if I use a cte to create a table of dates (defined by the input parameters), along with associated midnight and 9am census date/time points, then I could use these variables to group and evaluate the dataset.
So, this code generates the grouping dates and census date/time points:
DECLARE
#StartDate DATE = '01/09/2017'
,#EndDate DATE = '05/09/2017'
,#0900 INT = 540
SELECT
DATEADD(DAY, nbr - 1, #StartDate) [Date]
,CONVERT(DATETIME,(DATEADD(DAY, nbr - 1, #StartDate))) [MidnightDate]
,DATEADD(mi, #0900,(CONVERT(DATETIME,(DATEADD(DAY, nbr - 1, #StartDate))))) [0900Date]
FROM
(
SELECT
ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #StartDate, #EndDate)
The stumbling block I’ve hit is how to join the cte to the WardStays dataset, because there’s no appropriate key… I’ve tried a few iterations of using a subquery to make this work, but either I’m taking the wrong approach or I’m getting my syntax in a mess.
In simple terms, the logic I’m trying to create to get the output is something like:
SELECT
[Date]
,SUM (case when WST.Ward_Start_Date_Time <= [MidnightDate] AND (WST.Ward_End_Date_Time >= [MidnightDate] OR WST.Ward_End_Date_Time IS NULL then 1 else 0 end) [No. Beds Occupied at 00:00]
,SUM (case when WST.Ward_Start_Date_Time <= [0900Date] AND (WST.Ward_End_Date_Time >= [0900Date] OR WST.Ward_End_Date_Time IS NULL then 1 else 0 end) [No. Beds Occupied at 09:00]
FROM WardStaysTable WST
GROUP BY [Date]
Is the above somehow possible, or am I barking up the wrong tree and need to take a different approach altogether? Appreciate any advice.
I would expect something like this:
WITH dates as (
SELECT CAST(#StartDate as DATETIME) as dte
UNION ALL
SELECT DATEADD(DAY, 1, dte)
FROM dates
WHERE dte < #EndDate
)
SELECT dates.dte [Date],
SUM(CASE WHEN Ward_Start_Date_Time <= dte AND
Ward_END_Date_Time >= dte
THEN 1 ELSE 0
END) as num_beds_0000,
SUM(CASE WHEN Ward_Start_Date_Time <= dte + CAST('09:00' as DATETIME) AND
Ward_END_Date_Time >= dte + CAST('09:00' as DATETIME)
THEN 1 ELSE 0
END) as num_beds_0900
FROM dates LEFT JOIN
WardStaysTable wt
ON wt.Ward_Start_Date_Time <= DATEADD(day, 1, dates.dte) AND
wt.Ward_END_Date_Time >= dates.dte
GROUP BY dates.dte
ORDER BY dates.dte;
The cte is just creating the list of dates.
What a cool exercise. Here is what I came up with:
CREATE TABLE #tmp (ID int, StartDte datetime, EndDte datetime)
INSERT INTO #tmp values(1,'2017-09-03 15:04:00.000','2017-09-27 06:55:00.000')
INSERT INTO #tmp values(2,'2017-09-04 08:08:00.000','2017-09-06 18:00:00.000')
INSERT INTO #tmp values(3,'2017-09-04 13:00:00.000','2017-09-04 22:00:00.000')
INSERT INTO #tmp values(4,'2017-09-04 20:54:00.000','2017-09-08 14:30:00.000')
INSERT INTO #tmp values(5,'2017-09-04 20:52:00.000','2017-09-13 11:50:00.000')
INSERT INTO #tmp values(6,'2017-09-05 13:32:00.000','2017-09-11 14:49:00.000')
INSERT INTO #tmp values(7,'2017-09-05 13:17:00.000','2017-09-12 21:00:00.000')
INSERT INTO #tmp values(8,'2017-09-05 23:11:00.000','2017-09-06 07:38:00.000')
INSERT INTO #tmp values(9,'2017-09-05 11:35:00.000','2017-09-14 16:12:00.000')
INSERT INTO #tmp values(10,'2017-09-05 14:05:00.000','2017-09-11 16:30:00.000')
DECLARE
#StartDate DATE = '09/01/2017'
,#EndDate DATE = '10/01/2017'
, #nHours INT = 9
;WITH d(OrderDate) AS
(
SELECT DATEADD(DAY, n-1, #StartDate)
FROM (SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects) AS x(n)
)
, CTE AS(
select OrderDate, t2.*
from #tmp t2
cross apply(select orderdate from d ) d
where StartDte >= #StartDate and EndDte <= #EndDate)
select OrderDate,
SUM(CASE WHEN OrderDate >= StartDte and OrderDate <= EndDte THEN 1 ELSE 0 END) [No. Beds Occupied at 00:00],
SUM(CASE WHEN StartDTE <= DateAdd(hour,#nHours,CAST(OrderDate as datetime)) and DateAdd(hour,#nHours,CAST(OrderDate as datetime)) <= EndDte THEN 1 ELSE 0 END) [No. Beds Occupied at 09:00]
from CTE
GROUP BY OrderDate
This should allow you to check for any hour of the day using the #nHours parameter if you so choose. If you only want to see records that actually fall within your date range then you can filter the cross apply on start and end dates.

Converting INT to DATE then using GETDATE on conversion?

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...

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