How to find the difference between two dates in same column? - sql

I have a table SO_STATUS that writes a record for each status change for a service order (we'll call the Service_Order_ID "Job_ID"). Job_ID references SERVICE_ORDER table. When the service order is initialized, a record is written for that status type of "open" (StatusType 2) which shows the datetime. Then another record is written in the status table for when it is "in progress" (StatusType 1). And also when the service order is "closed", another record written in the status table (StatusType 3). There are also other status types that may happen, but these are the most common. The data in the SO_STATUS table looks like this:
id Date Job_ID StatusTypeID EmployeeID
1 2012-01-01 09:05:00.000 51 2 5
2 2012-01-01 10:00:00.000 52 2 12
3 2012-01-01 10:01:00.000 51 1 5
4 2012-01-01 12:15:00.000 53 2 8
5 2012-01-01 12:16:00.000 51 3 5
6 2012-01-01 13:00:00.000 52 1 12
7 2012-01-01 14:00:00.000 52 3 12
8 2012-01-01 14:15:00.000 53 1 8
9 2012-01-01 15:00:00.000 54 2 11
10 2012-01-01 16:30:00.000 53 3 8
11 2012-01-01 15:00:00.000 54 1 11
12 2012-01-01 16:30:00.000 54 3 11
I need to be able to find the time elapsed between each status change of each Job_ID. Essentially, the duration of time spent from open to close for the job.
Output would look something like (EmployeeName would be referenced from the EMPLOYEE table):
Job_ID Duration EmployeeName
51 03:11:00 Kyle
52 04:00:00 Chris
53 04:15:00 Fred
54 01:30:00 John
How would I go about getting this type of output? Thank you.

Why dont you use:
SELECT DATEDIFF (anyparticularunit, ' 2012-01-01 09:05:00.000', ' 2012-01-01 15:00:00.000')
Go through following link for datediff:
http://msdn.microsoft.com/en-us/library/ms189794.aspx
Also follow this link to get different exmples:
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=56126
Hope you will put further where conditions.

This this one -
SET NOCOUNT ON;
DECLARE #duration TABLE
(
id BIGINT IDENTITY
, [date] DATETIME
, job_id INT
, [status] VARCHAR(10)
, employee_id INT
)
INSERT INTO #duration ([date], job_id, [status], employee_id)
VALUES
('2012-01-01 09:05:00.000', 51, 'open', 5),
('2012-01-01 10:00:00.000', 52, 'open', 12),
('2012-01-01 10:01:00.000', 51, 'inprogress', 5),
('2012-01-01 12:15:00.000', 53, 'open', 8),
('2012-01-01 12:16:00.000', 51, 'closed', 5),
('2012-01-01 13:00:00.000', 52, 'inprogress', 12),
('2012-01-01 14:00:00.000', 52, 'closed', 12),
('2012-01-01 14:15:00.000', 53, 'inprogress', 8),
('2012-01-01 15:00:00.000', 54, 'open', 11),
('2012-01-01 16:30:00.000', 53, 'closed', 8),
('2012-01-01 15:00:00.000', 54, 'inprogress', 11),
('2012-01-01 16:30:00.000', 54, 'closed', 11)
SELECT
job_id
, employee_id
, work_time = CONVERT(VARCHAR(12), MAX([date]) - MIN([date]), 114)
FROM #duration
GROUP BY job_id, employee_id

You can use DATEDIFF to return the count (signed integer) of the specified datepart boundaries crossed between the specified startdate and enddate (see http://msdn.microsoft.com/en-us/library/ms189794.aspx)
SELECT Job_ID,
DATEDIFF(day, (SELECT MIN(Date) FROM YOUTABLE WHERE Job_ID=k.Job_ID),(SELECT MAX(Date) FROM YOUTABLE WHERE Job_ID=k.Job_ID)),
(SELECT EmployeeName FROM EmployeeTABLE WHERE EmployeeID=k.EmployeeID)) FROM YOUTABLE k

If your database is Oracle, you can do like this
SELECT DISTINCT JOB_ID, MAX(DATE) OVER(PARTITION BY JOB_ID)-MIN(DATE) OVER(PARTITION BY JOB_ID) AS Duration FROM TA JOIN TB .....

I have created some custom code to create dat and time difference, using datediff function and dividing with certain numbers to generate hours, minutes and seconds:
SELECT
Job_ID,
CAST(DATEDIFF(second, MIN(Date), MAX(Date)) / 3600 AS VARCHAR)
+ ':' + CAST((DATEDIFF(second, MIN(Date), MAX(Date)) % 3600) / 60 AS VARCHAR)
+ ':' + CAST(((DATEDIFF(second, MIN(Date), MAX(Date)) % 3600) % 60) AS VARCHAR)
FROM YOUTABLE
GROUP BY Job_ID

Try query given below:
Select t1.Job_ID,
Convert(varchar(5),DateDiff(HH,Min(t1.JobDate),tbl.MaxDate))+' : '+convert(varchar(5),DateDiff(s,Min(t1.JobDate),tbl.MaxDate) % 3600/60)+' : '+Convert(varchar(5),DateDiff(s,Min(t1.JobDate),
tbl.MaxDate) % 60) MinDate,t1.EmployeeName From SO_STATUS t1
Inner join (Select Max(JobDate) MaxDate, job_id From SO_STATUS Group By Job_Id)tbl on t1.Job_ID=tbl.Job_ID
Inner Join EMPLOYEE e On e.EmployeeID=t1.EmployeeID
Group By t1.EmployeeName,tbl.MaxDate,t1.Job_ID
Order By t1.Job_ID

Difference between two dates of different tables which has datetime format.
SELECT t1.Column_Names,
CONVERT(varchar(10),t1.CreatedOn,103)
AS CreatedOn FROM table1 t1 INNER JOIN table2 t2
ON t1.id = t2.id
WHERE CAST (t1.CreatedOn as Date)
BETWEEN #fromdate and #todate.
i have taken t1.CreatedOn as my table attribute which holds date.
#fromdate and #todate to pass dates.

Related

Histogram of orders by range of dates

I'm trying to create a histogram based on interval of dates and total number of orders but im having a hard time binning it through SQL.
A simplified table can be seen below
customer_id
Date
count_orders
1
01-01-2020
5
1
01-13-2020
26
1
02-06-2020
11
2
01-17-2020
9
3
02-04-2020
13
3
03-29-2020
24
4
04-05-2020
1
5
02-23-2020
10
6
03-15-2020
7
6
04-18-2020
32
...
...
...
and im thinking of binning it into 20 day intervals but the only thing I can think about is do a
SUM(CASE WHEN Date BETWEEN <interval1_startdate> AND <interval1_enddate> ...)
method per interval which if used into the actual data (which contains millions of row) is quite exhausting. So i need help in automating the binning part.
Desired output would either be
1)
interval
total_count
01-01-2020 - 01-20-2020
31
01-21-2020 - 02-10-2020
24
02-10-2020 - 03-01-2020
10
...
...
or 2)
start
end
total_count
01-01-2020
01-20-2020
31
01-21-2020
02-10-2020
24
02-10-2020
03-01-2020
10
...
...
...
Do you have any ideas?
You can group by the (current date - minimum date)/20. For preso something like this:
WITH dataset (customer_id, Date, count_orders) AS (
VALUES (1, date_parse('01-01-2020', '%m-%d-%Y'), 5),
(1, date_parse('01-13-2020', '%m-%d-%Y'), 26),
(1, date_parse('02-06-2020', '%m-%d-%Y'), 11),
(2, date_parse('01-17-2020', '%m-%d-%Y'), 9),
(3, date_parse('02-04-2020', '%m-%d-%Y'), 13),
(3, date_parse('03-29-2020', '%m-%d-%Y'), 24),
(4, date_parse('04-05-2020', '%m-%d-%Y'), 1),
(5, date_parse('02-23-2020', '%m-%d-%Y'), 10),
(6, date_parse('03-15-2020', '%m-%d-%Y'), 7),
(6, date_parse('04-18-2020', '%m-%d-%Y'), 32)
)
SELECT date_add('day', 20 * grp, min(min_date)) interval_end,
date_add('day', 20 * (grp + 1) - 1, min(min_date)) interval_end,
sum(count_orders) total_count
FROM (
SELECT *,
date_diff('day', min(date) over (), date) / 20 as grp,
min(date) over () min_date
FROM dataset
)
group by grp
order by 1
Output:
interval_end
interval_end
total_count
2020-01-01 00:00:00.000
2020-01-20 00:00:00.000
40
2020-01-21 00:00:00.000
2020-02-09 00:00:00.000
24
2020-02-10 00:00:00.000
2020-02-29 00:00:00.000
10
2020-03-01 00:00:00.000
2020-03-20 00:00:00.000
7
2020-03-21 00:00:00.000
2020-04-09 00:00:00.000
25
2020-04-10 00:00:00.000
2020-04-29 00:00:00.000
32
You can get the intervals using CTE and then get the total using cross apply.
Drop table Tbl
Create Table Tbl (customer_id Int, [date] Date, count_orders Int)
Insert Into Tbl (customer_id, [date], count_orders)
Values (1,'2020-01-01', 5),
(1,'2020-01-13',26),
(1,'2020-02-06',11),
(2,'2020-01-17',9),
(3,'2020-02-04',13),
(3,'2020-03-29',24),
(4,'2020-04-05',1),
(5,'2020-02-23',10),
(6,'2020-03-15',7),
(6,'2020-04-18',32)
;With A As (
Select Min([date]) As start, DateAdd(dd,19,Min([date])) As [end], Max([date]) As [max]
From Tbl
Union All
Select DateAdd(dd,1,[end]) As start, DateAdd(dd,20,[end]) As [end], [max]
From A
Where [end]<[max])
Select A.[start], A.[end], T.total_count
From A Cross Apply (Select SUM(count_orders) As total_count
From Tbl Where [date] between A.[start] And A.[end]) As T
Result:
start end total_count
---------- ---------- -----------
2020-01-01 2020-01-20 40
2020-01-21 2020-02-09 24
2020-02-10 2020-02-29 10
2020-03-01 2020-03-20 7
2020-03-21 2020-04-09 25
2020-04-10 2020-04-29 32

Group by on range of dates

I've read some topics about group by sequence but I could not figure out an solution for my problem.
I have a table (the name is ViewHistory) like this.
Tme Value
2020-07-22 09:30:00 1
2020-07-22 09:31:00 2
2020-07-22 09:32:00 3
2020-07-22 09:33:00 4
2020-07-22 09:34:00 5
2020-07-22 09:35:00 6
.
.
.
The data can grow indefinitely.
In this table, there are many records with 1 min TimeFrame.
I want to group on range of dataTime with timeFrame 2 min and Sum(value).
like this output:
TimeFrame SumData
09:30 1
09:32 5 -- sum of range 09:31_09:32
09:34 9 -- sum of range 09:33_09:34
.
.
.
How can I do this automatically, instead of using a:
WHERE Tme BETWEEN ('2020-07-22 09:31:00' AND '2020-07-22 09:32:00') and etc.
I am sure there is a simpler way, but its not coming to me right now.
declare #Test table (tme datetime2, [value] int)
insert into #Test (tme, [value])
values
('2020-07-22 09:30:00', 1),
('2020-07-22 09:31:00', 2),
('2020-07-22 09:32:00', 3),
('2020-07-22 09:33:00', 4),
('2020-07-22 09:34:00', 5),
('2020-07-22 09:35:00', 6);
with cte as (
select convert(date, tme) [date], datepart(hour, tme) [hour], datepart(minute,dateadd(minute, 1,tme)) / 2 [minute], sum([value]) [value]
from #Test
group by convert(date, tme), datepart(hour, tme), datepart(minute,dateadd(minute, 1,tme)) / 2
)
select convert(varchar(2),[hour]) + ':' + convert(varchar(2), [minute] * 2) [time], [value]
-- , dateadd(minute, [minute] * 2, dateadd(hour, [hour], convert(datetime2, [date]))) -- Entire date if desired
from cte;
Which gives:
time
value
9:30
1
9:32
5
9:34
9
9:36
6

know in which interval of dates of 15 minutes is a date SQL SERVER

sql fiddle example
I have this table structure :
CREATE TABLE TIMETABLE
([ID] int, [Name] varchar(50), [StartDate] datetime, [EndDate] datetime)
;
INSERT INTO TIMETABLE
([ID], [Name], [StartDate], [EndDate])
VALUES
(1, 'John', '2017-01-29 16:00:00.000', '2017-01-29 16:12:00.000'),
(2, 'Mario', '2017-01-29 16:17:00.000', '2017-01-29 16:29:00.000'),
(3, 'Kate', '2017-01-15 10:35:00.000', '2017-01-15 10:40:00.000'),
(4, 'Maria', '2017-01-15 10:17:00.000', '2017-01-15 10:27:00.000'),
(5, 'Oliver', '2017-01-15 13:46:00.000', '2017-01-29 14:00:00.000')
;
And The result for this :
select * from TIMETABLE
ID Name StartDate EndDate
1 John 2017-01-29T16:00:00Z 2017-01-29T16:12:00Z
2 Mario 2017-01-29T16:17:00Z 2017-01-29T16:29:00Z
3 Kate 2017-01-15T10:35:00Z 2017-01-15T10:40:00Z
4 Maria 2017-01-15T10:17:00Z 2017-01-15T10:27:00Z
5 Oliver 2017-01-15T13:46:00Z 2017-01-29T14:00:00Z
I want to know with a range from 15 mins in wich range is the date, for example:
ID Name StartDate EndDate HourRangeTime
1 John 2017-01-29T16:00:00Z 2017-01-29T16:12:00Z 16:00
In the example the startdate and the enddate is in the range between 16:00 and 16:12 is in the range 16:00
The result it should be like this:
ID Name StartDate EndDate HourRangeTime
1 John 2017-01-29T16:00:00Z 2017-01-29T16:12:00Z 16:00
2 Mario 2017-01-29T16:17:00Z 2017-01-29T16:29:00Z 16:15
3 Kate 2017-01-15T10:35:00Z 2017-01-15T10:40:00Z 10:30
4 Maria 2017-01-15T10:17:00Z 2017-01-15T10:27:00Z 10:15
5 Oliver 2017-01-15T13:46:00Z 2017-01-29T14:00:00Z 13:45
How can I fill the column HourRangeTime, take dates and see what range does it belong to?
Your seem focused on the StartDate.
A relatively general way to do this is to convert this to minutes and then truncate the minutes to the nearest 15 minutes. Here is code:
select cast(dateadd(minute,
15 * (datediff(minute, 0,
cast(StartDate as time)
) / 15
), 0
) as time)
This returns the result as a time.
You can get difference and process future.
SELECT StartTime, EndTime, DATEDIFF(MINUTE, StartTime , EndTime) AS MinuteDiff
FROM TIMETABLE
You can try this for your desired output:
SELECT
CONCAT(DATEPART(hh,StartDate), ':',
CASE
WHEN DATEPART(MINUTE,StartDate) BETWEEN 0 AND 14 THEN '00'
WHEN DATEPART(MINUTE,StartDate) BETWEEN 15 AND 29 THEN '15'
WHEN DATEPART(MINUTE,StartDate) BETWEEN 30 AND 44 THEN '30'
WHEN DATEPART(MINUTE,StartDate) BETWEEN 45 AND 59 THEN '45'
ELSE '00'
END) AS HourRangeTime
FROM TIMETABLE
OUTPUT:
HourRangeTime
-------------
16:00
16:15
10:30
10:15
13:45
You can use this.
SELECT *,
CONVERT(VARCHAR,DATEPART(HOUR, [StartDate]))
+ ':'
+ RIGHT(CONVERT(VARCHAR,(DATEPART(MINUTE, [StartDate]) / 15) * 15)+'0',2) HourRangeTime FROM TIMETABLE

Calculate total time worked in a day with multiple stops and starts

I can use DATEDIFF to find the difference between one set of dates like this
DATEDIFF(MINUTE, #startdate, #enddate)
but how would I find the total time span between multiple sets of dates? I don't know how many sets (stops and starts) I will have.
The data is on multiple rows with start and stops.
ID TimeStamp StartOrStop TimeCode
----------------------------------------------------------------
1 2017-01-01 07:00:00 Start 1
2 2017-01-01 08:15:00 Stop 2
3 2017-01-01 10:00:00 Start 1
4 2017-01-01 11:00:00 Stop 2
5 2017-01-01 10:30:00 Start 1
6 2017-01-01 12:00:00 Stop 2
This code would work assuming that your table only store data from one person, and they should be of the order Start/Stop/Start/Stop
WITH StartTime AS (
SELECT
TimeStamp
, ROW_NUMBER() PARTITION BY (ORDER BY TimeStamp) RowNum
FROM
<<table>>
WHERE
TimeCode = 1
), StopTime AS (
SELECT
TimeStamp
, ROW_NUMBER() PARTITION BY (ORDER BY TimeStamp) RowNum
FROM
<<table>>
WHERE
TimeCode = 2
)
SELECT
SUM (DATEDIFF( MINUTE, StartTime.TimeStamp, StopTime.TimeStamp )) As TotalTime
FROM
StartTime
JOIN StopTime ON StartTime.RowNum = StopTime.RowNum
This will work if your starts and stops are reliable. Your sample has two starts in order - 10:00 and 10:30 starts. I assume in production you will have an employee id to group on, so I added this to the sample data in place of the identity column.
Also in production, the CTE sets will be reduced by using a parameter on date. If there are overnight shifts, you would want your stops CTE to use dateadd(day, 1, #startDate) as your upper bound when retrieving end date.
Set up sample:
declare #temp table (
EmpId int,
TimeStamp datetime,
StartOrStop varchar(55),
TimeCode int
);
insert into #temp
values
(1, '2017-01-01 07:00:00', 'Start', 1),
(1, '2017-01-01 08:15:00', 'Stop', 2),
(1, '2017-01-01 10:00:00', 'Start', 1),
(1, '2017-01-01 11:00:00', 'Stop', 2),
(2, '2017-01-01 10:30:00', 'Start', 1),
(2, '2017-01-01 12:00:00', 'Stop', 2)
Query:
;with starts as (
select t.EmpId,
t.TimeStamp as StartTime,
row_number() over (partition by t.EmpId order by t.TimeStamp asc) as rn
from #temp t
where Timecode = 1 --Start time code?
),
stops as (
select t.EmpId,
t.TimeStamp as EndTime,
row_number() over (partition by t.EmpId order by t.TimeStamp asc) as rn
from #temp t
where Timecode = 2 --Stop time code?
)
select cast(min(sub.StartTime) as date) as WorkDay,
sub.EmpId as Employee,
min(sub.StartTime) as ClockIn,
min(sub.EndTime) as ClockOut,
sum(sub.MinutesWorked) as MinutesWorked
from
(
select strt.EmpId,
strt.StartTime,
stp.EndTime,
datediff(minute, strt.StartTime, stp.EndTime) as MinutesWorked
from starts strt
inner join stops stp
on strt.EmpId = stp.EmpId
and strt.rn = stp.rn
)sub
group by sub.EmpId
This works assuming your table has an incremental ID and interleaving start/stop records
--Data sample as provided
declare #temp table (
Id int,
TimeStamp datetime,
StartOrStop varchar(55),
TimeCode int
);
insert into #temp
values
(1, '2017-01-01 07:00:00', 'Start', 1),
(2, '2017-01-01 08:15:00', 'Stop', 2),
(3, '2017-01-01 10:00:00', 'Start', 1),
(4, '2017-01-01 11:00:00', 'Stop', 2),
(5, '2017-01-01 10:30:00', 'Start', 1),
(6, '2017-01-01 12:00:00', 'Stop', 2)
--let's see every pair start/stop and discard stop/start
select start.timestamp start, stop.timestamp stop,
datediff(mi,start.timestamp,stop.timestamp) minutes
from #temp start inner join #temp stop
on start.id+1= stop.id and start.timecode=1
--Sum all for required result
select sum(datediff(mi,start.timestamp,stop.timestamp) ) totalMinutes
from #temp start inner join #temp stop
on start.id+1= stop.id and start.timecode=1
Results
+-------------------------+-------------------------+---------+
| start | stop | minutes |
+-------------------------+-------------------------+---------+
| 2017-01-01 07:00:00.000 | 2017-01-01 08:15:00.000 | 75 |
| 2017-01-01 10:00:00.000 | 2017-01-01 11:00:00.000 | 60 |
| 2017-01-01 10:30:00.000 | 2017-01-01 12:00:00.000 | 90 |
+-------------------------+-------------------------+---------+
+--------------+
| totalMinutes |
+--------------+
| 225 |
+--------------+
Maybe the tricky part is the join clause. We need to join #table with itself by deferring 1 ID. Here is where on start.id+1= stop.id did its work.
In the other hand, for excluding stop/start couple we use start.timecode=1. In case we don't have a column with this information, something like stop.id%2=0 works just fine.

SQL Server - Selecting periods without changes in data

What I am trying to do is to select periods of time where the rest of data in the table was stable based on one column and check was there a change in second column value in this period.
Table:
create table #stable_periods
(
[Date] date,
[Car_Reg] nvarchar(10),
[Internal_Damages] int,
[External_Damages] int
)
insert into #stable_periods
values ('2015-08-19', 'ABC123', 10, 10),
('2015-08-18', 'ABC123', 9, 10),
('2015-08-17', 'ABC123', 8, 9),
('2015-08-16', 'ABC123', 9, 9),
('2015-08-15', 'ABC123', 10, 10),
('2015-08-14', 'ABC123', 10, 10),
('2015-08-19', 'ABC456', 5, 3),
('2015-08-18', 'ABC456', 5, 4),
('2015-08-17', 'ABC456', 8, 4),
('2015-08-16', 'ABC456', 9, 4),
('2015-08-15', 'ABC456', 10, 10),
('2015-01-01', 'ABC123', 1, 1),
('2015-01-01', 'ABC456', NULL, NULL);
--select * from #stable_periods
-- Unfortunately I can’t post pictures yet but you get the point of how the table looks like
What I would like to receive is
Car_Reg FromDate ToDate External_Damages Have internal damages changed in this period?
ABC123 2015-08-18 2015-08-19 10 Yes
ABC123 2015-08-16 2015-08-17 9 Yes
ABC123 2015-08-14 2015-08-15 10 No
ABC123 2015-01-01 2015-01-01 1 No
ABC456 2015-08-19 2015-08-19 3 No
ABC456 2015-08-16 2015-08-18 4 Yes
ABC456 2015-08-15 2015-08-15 10 No
ABC456 2015-01-01 2015-01-01 NULL NULL
Basically to build period frames where [External_Damages] were constant and check did the [Internal_Damages] change in the same period (doesn't matter how many times).
I spend a lot of time trying but I am afraid that my level of abstraction thinking in much to low...
Will be great to see any suggestions.
Thanks,
Bartosz
I believe this is a form of Islands Problem.
Here is a solution using ROW_NUMBER and GROUP BY:
SQL Fiddle
WITH CTE AS(
SELECT *,
RN = DATEADD(DAY, - ROW_NUMBER() OVER(PARTITION BY Car_reg, External_Damages ORDER BY [Date]), [Date])
FROM #stable_periods
)
SELECT
Car_Reg,
FromDate = MIN([Date]),
ToDate = MAX([Date]) ,
External_Damages,
Change =
CASE
WHEN MAX(External_Damages) IS NULL THEN NULL
WHEN COUNT(DISTINCT Internal_Damages) > 1 THEN 'Yes'
ELSE 'No'
END
FROM CTE c
GROUP BY Car_Reg, External_Damages, RN
ORDER BY Car_Reg, ToDate DESC