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

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.

Related

Date and shift time type filter in stored procedure

I am using SQL Server 2016 and I have a sensor table as follow:
SensorCode
SensorStatus
Timestamp
PS01A
Active
2019-11-20 01:38:11.850
PS01B
Active
2019-11-20 02:30:09.850
PS01C
Active
2019-11-20 05:32:11.004
PS01D
Active
2019-11-20 07:38:07.997
PS01E
Active
2019-11-20 11:38:06.700
How can I filter above table with date and shift type as input parameter in stored procedure? I can make the query for filter the date but not the shift type.
For the shift type use below table as reference:
ShiftType
StartTime
EndTime
Day
06:00:00
17:59:59
Night
18:00:00
05:59:59
and here is my code
ALTER PROCEDURE [dbo].[GET_SENSOR_STATE]
#date DATE,
#shifttypeid INT,
#shifttype NVARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
IF (#shifttype = 'DAY')
SET #shifttypeid = 0
ELSE
SET #shifttypeid = 1
SELECT [SensorCode]
,[SensorStatus]
,[Timestamp]
FROM [MyTable].[dbo].[SensorState] SS
WHERE
--#date = CAST(SS.Timestamp AS DATE)
CASE #shifttypeid
WHEN 0 THEN #date = CAST(SS.Timestamp AS DATE) AND DATEPART(HH, SS.Timestamp) BETWEEN 6 AND 18
WHEN 1 THEN #date = CAST(SS.Timestamp AS DATE) AND DATEPART(HH, SS.Timestamp) BETWEEN 18 AND 6
END
END
You can't use BETWEEN 18 AND 6 it will never be true. Also for BETWEEN, the checking condition is inclusive BETWEEN 6 AND 18 will means >= 6 AND <= 18 which is not exactly what you want. It is best to use >= and <
WHERE (
CAST(SS.Timestamp AS DATE) = #date
OR (
#shifttypeid = 1
AND CAST(SS.Timestamp AS DATE) = DATEADD(DAY, 1, #date)
)
)
AND (
( -- Day Shift
#shifttypeid = 0
AND DATEPART(HH, SS.Timestamp) >= 6
AND DATEPART(HH, SS.Timestamp) < 18
)
OR ( -- Night Shift
#shifttypeid = 1
AND (
DATEPART(HH, SS.Timestamp) < 6
OR DATEPART(HH, SS.Timestamp) >= 18
)
)
)
Added condition to handle Night Shift Date
EDIT : based on #shifttypeid filter the required Timestamp accordingly.
WHERE ( -- Day Shift 06:00 to 18:00
#shifttypeid = 0
AND SS.Timestamp >= DATEADD(HOUR, 6, CAST(#date AS DATETIME))
AND SS.Timestamp < DATEADD(HOUR, 18, CAST(#date AS DATETIME))
)
OR ( -- Night Shfit 18:00 to 06:00
#shifttypeid = 1
AND SS.Timestamp >= DATEADD(HOUR, 18, CAST(#date AS DATETIME))
AND SS.Timestamp < DATEADD(HOUR, 30, CAST(#date AS DATETIME))
)

Sql Server - Count occurrences of datetime group by specific interval

I have this table with sample data:
MY_TABLE
------------------------------------------
ID DateVal other columns
------------------------------------------
1 2017-01-14 11:00:00 ...
2 2017-01-14 11:01:00 ...
3 2017-01-14 11:02:00 ...
4 2017-01-14 11:03:00 ...
5 2017-01-14 11:11:00 ...
6 2017-01-14 11:11:30 ...
7 2017-01-14 11:15:00 ...
8 2017-01-14 11:15:01 ...
9 2017-01-14 11:18:00 ...
I need to have this kind of result:
start end occurrences
-----------------------------------------------------------
2017-01-14 11:00 2017-01-14 11:05 4
2017-01-14 11:05 2017-01-14 11:10 0
2017-01-14 11:10 2017-01-14 11:15 3
2017-01-14 11:15 2017-01-14 11:20 2
...
In specific I need a query that extracts all the occurrences of raws in MY_TABLE in 5 mins range (range value is variable).
Someone could help me?
Best regards,
You need to generate the timeframes you want and then left join. Here is one method:
select v.dt, dateadd(minute, 5, v.dt) as end_dt, count(t.id)
from (values (convert(datetime, '2017-01-14 11:00')),
(convert(datetime, '2017-01-14 11:05')),
. . .
) v(dt) left join
my_table t
on t.dateval >= v.dt and
t.dateval < dateadd(minute, 5, v.dt)
group by v.dt;
Note: If you want to do this for a wider range of time, then using a tally table or recursive CTE is handy.
Let's have a row generator, that generates dates every rangeSize from a startDate:
DECLARE #rangeSize INT = 5;
DECLARE #startDate DATETIME = '2020-01-01 00:00';
WITH RG(D,D2) AS (
SELECT #startDate AS D, DATEADD(MINUTE, #rangeSize, #startDate) AS D2
UNION ALL
SELECT DATEADD(MINUTE, #rangeSize, D), DATEADD(MINUTE, #rangeSize, D2)
FROM RG a
WHERE D < DATEADD(MINUTE, #rangeSize * 100, #startDate)
)
SELECT D,D2
FROM RG
OPTION (MAXRECURSION 100);
Now let's hook it up to your data and count the data:
DECLARE #rangeSize INT = 5;
DECLARE #startDate DATETIME = '2020-01-01 00:00';
WITH RG(D,D2) AS (
SELECT #startDate AS D, DATEADD(MINUTE, #rangeSize, #startDate) AS D2
UNION ALL
SELECT DATEADD(MINUTE, #rangeSize, D), DATEADD(MINUTE, #rangeSize, D2)
FROM RG a
WHERE D < DATEADD(MINUTE, #rangeSize * 100, #startDate)
)
SELECT r.D as StartDate, r.D2 as EndDate, COUNT(m.ID) as Count as EndDate
FROM
RG r
LEFT JOIN
my_table m ON m.dateval > r.D AND m.dateval <= r.D2
GROUP BY r.D, r.D2
OPTION (MAXRECURSION 100);
Note I used > and <= for the range because you seem to classify from e.g 15:01 to 20:00 as a range, whereas it feels more natural to me to have 15:00 to 19:59 as "belonging to the 15-20 range"
If you don't need your start and end to reflect the exact range:
You can use something like DATEDIFF(minute, '2000-01-01', DateVal)/5 as your grouping, then use MIN(DateVal) and MAX(DateVal) for your start and end; but those values will be of the first and last transaction in the interval, not the bounds of the interval.
Alternatively, you can use a recursive CTE to generate the intervals, and then join that to your data:
; WITH intervals AS (
SELECT CAST('2017-11-01 00:00:00' AS DATETIME) AS `start`
, CAST ('2017-11-01 00:05:00' AS DATETIME) AS `end`
UNION ALL
SELECT DATEADD(minute, 5, `start`) AS, DATEADD(minute, 5, `end`) AS end
FROM intervals
WHERE intervals.end < '2017-11-02 00:00:00'
)
SELECT i.`start`, i.`end`, COUNT(t.ID) AS occurrences
FROM intervals AS i
INNER JOIN MY_TABLE AS t ON t.DateVal >= i.`start` AND t.DateVal < i.End
GROUP BY i.`start`, i.`end`
ORDER BY i.`start`, i.`end`
;
Notes:
the literal date values can be adjusted to reflect the actual range you want to query
If you want intervals without activity to be included, the INNER can be changed to LEFT
Since your question as stated has overlapping intervals, I went with the assumption that a DateVal on the 5 minute mark belongs to the interval that starts with that value.

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.

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

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