How to convert sql dates range to Daily Row - sql

How do I convert a date range so each day is 1 row with the start and end time for that day?
I looked at this post about date ranges to row - but this is a different problem. The other solution linked above does not give the time from start to finish for each day - and thus does not allow for duty factor or utilization calculations, and or the build of a Gantt chart.
We would have an ID field, a Start Date, and an End Date as our base table. We want to convert this to contain the ID Field per day with how much time was consumed in that range.
This is very useful when converting a start and end dates to daily duty factor and a host of other needs like equipment utilization.

I had a lot of help from this community figuring this out. I wanted to put the final SQL script here for others to use as well.
WITH cte ([VID],[StartTime],[EndTime]) AS
( SELECT tbl.[ID] as 'VID'
,CONVERT(VARCHAR(19), tbl.[StartDT], 120) AS 'StartTime'
,CASE
WHEN tbl.[EndDT] <= CONVERT(VARCHAR(11), tbl.[StartDT]+1, 120) + '00:00:00' THEN CONVERT(VARCHAR(19), tbl.[EndDT], 120)
ELSE CONVERT(VARCHAR(11), tbl.[StartDT]+1, 120) + '00:00:00'
END as 'EndTime'
FROM [SourceTable] as tbl
WHERE DATEDIFF(DAY,tbl.[StartDT],tbl.[EndDT] )<=365
UNION ALL
SELECT tbl.[ID] as 'VID'
,CONVERT(VARCHAR(11), DATEADD(DAY, 1, cte.[StartTime]), 120) + '00:00:00' AS 'StartTime'
,CASE
WHEN CONVERT(VARCHAR(19), tbl.[EndDT], 120) < CONVERT(VARCHAR(11), DATEADD(DAY, 2, cte.[StartTime]), 120) + '00:00:00'
THEN CONVERT(VARCHAR(19), tbl.[EndDT], 120)
ELSE CONVERT(VARCHAR(11), DATEADD(DAY, 2, cte.[StartTime]), 120) + '00:00:00'
END AS 'EndTime'
FROM cte
INNER JOIN [SourceTable] as tbl
ON cte.VID = tbl.ID
WHERE CONVERT(VARCHAR(11), cte.[StartTime], 120) < CONVERT(VARCHAR(11), tbl.[EndDT], 120))
SELECT VID AS ID
,[StartTime]
,[EndTime]
,DateDiff (second,[StartTime],[EndTime]) / 3600 As 'Hours'
,DateDiff (second,[StartTime],[EndTime])/60 % 60 as 'Minutes'
,((DateDiff (Second,[StartTime],[EndTime]) / 3600)*60)+(DateDiff (second,starttime,endtime)/60 % 60) as 'Total Minutes'
,DATEPART(week,[StartTime]) AS weeknum
,MONTH([StartTime]) AS MonthNumber
,DATENAME(month,[StartTime]) AS MonthName
FROM cte order by Id, [StartTime]
option (maxrecursion 0);

Related

Get sum of qty based on hour window

I'm working on a problem to figure out how much qty of product was created and dispatched between certain hours. For example, I need to see how much was created (by created I mean how many orders were created with X qty) between 6pm today and 3pm tomorrow. I'm trying to create a time bin for this but whatever I try isn't working out.
select CREATE_DATE
, CREATE_TIME
, RELEASED_DATE
, RELEASED_TIME
, sum(case
when CREATE_DATE = DATEADD(DAY, DATEDIFF(DAY, 0, CREATE_DATE), 0)
and CREATE_TIME >= '18:00:00' AND CREATE_DATE = DATEADD(DAY, DATEDIFF(DAY, 0, CREATE_DATE), 1)
then ORDER_QTY
when CREATE_DATE = DATEADD(DAY, DATEDIFF(DAY, 0, CREATE_DATE), 1)
and CREATE_TIME <= '14:59:59'
then ORDER_QTY
end) as small_window_qty
, sum(ORDER_QTY) as ord_qty
, sum(RELEASED_QTY) as rls_qty
from table
Any help with this would be appreciated. Just need a way to organize the days into the following buckets: Normal Hour Window = Created 6pm to 6pm; Smaller Hour Window = Created 6pm to 3pm; Agreed Upon = Dispatch by 3pm (12am to 3pm)
Edit: Some clarification. What im trying to accomplish is a root cause analysis. We have orders that create every day, and must ship within 2 days of being created. We're trying to figure out why our orders are not shipping on time. So as an RCA, Im trying to dig into the orders, when they were created, when they were dispatched(or released, same thing) and when they shipped. The Hour Window's mentioned above are cutoff times for orders to be created for a certain day. Example:
300 units were created today, and they must ship 2 days from now. I want to see, of that 300 created, how many were created before 3pm, and of that, how much dispatched by 3pm same day. Hope that clarifies things. Not everything that is created must be dispatched the same day, as we have 2 days to ship the orders.
you're leaving a lot of info out so i'm filling in the blanks and making assumptions here. doublecheck that my data types line up with your data types.
create table sales (create_date date, create_time time, released_date date, released_time time, qty int)
insert sales
select '1/14/13','18:45','1/15/13','10:45', 10
union all
select '1/14/13','19:45','1/15/13','12:45', 12
union all
select '1/15/13','19:15','1/16/13','16:45', 6
union all
select '1/15/13','18:00','1/16/13','14:45', 25
union all
select '1/15/13','18:45','1/16/13','15:00', 3
union all
select '1/16/13','19:45','1/17/13','16:30', 1
union all
select '1/16/13','20:45','1/17/13','17:45', 9
union all
select '1/17/13','18:30','1/18/13','18:00', 17
union all
select '1/18/13','18:30','1/19/13','17:15', 15
union all
select '1/18/13','18:45','1/19/13','19:15', 21
with base as
(
select *
, cast(create_date as datetime) + cast(create_time as datetime) as createtime
, cast(released_date as datetime) + cast(released_time as datetime) as releasetime
, datediff(hour,cast(create_date as datetime) + cast(create_time as datetime),cast(released_date as datetime) + cast(released_time as datetime)) as hrs
from sales
),
base2 as
(
select qty
, case
when create_time >= '18:00' and hrs <= 21 then 'small'
when create_time >= '18:00' and hrs <= 24 then 'normal'
else 'outside'
end as orderwindow
, case
when hrs between 6 and 21 then 'pass'
else 'fail'
end as agreedupon
from base
)
select sum(qty) as qty, orderwindow, agreedupon
from base2
group by orderwindow, agreedupon
drop table sales
this should give you the end result of being able to tell how much was created, what window of time it falls into, and if released by 3pm. adjust as needed.
i didn't want the code to get messy and convoluted so i used 2 CTEs.
I am not sure about your expected result and also your input data looks messay and not clear. So I used the data provided by the above answer. It looks you get running sum. If you can review the answer and get back to me if you need more modifications that is good. I suggest you to edit your Question and be more clear with your sample data and expected answer. Add more details to the question.
create table sales (create_date date, create_time time, released_date date, released_time time, qty int)
INSERT INTO sales
select '1/14/13','18:45','1/15/13','10:45', 10
union all
select '1/14/13','19:45','1/15/13','12:45', 12
union all
select '1/15/13','19:15','1/16/13','16:45', 6
union all
select '1/15/13','18:00','1/16/13','14:45', 25
union all
select '1/15/13','18:45','1/16/13','15:00', 3
union all
select '1/16/13','19:45','1/17/13','16:30', 1
union all
select '1/16/13','20:45','1/17/13','17:45', 9
union all
select '1/17/13','18:30','1/18/13','18:00', 17
union all
select '1/18/13','18:30','1/19/13','17:15', 15
union all
select '1/18/13','18:45','1/19/13','19:15', 21
SELECT
create_date
, create_time
, released_date
, released_time
,SUM(QTY) OVER ( PARTITION BY A.[Dispatched Window] ORDER BY released_date ) AS [Sum_qty]
,Qty
FROM
(
SELECT
create_date
, create_time
, released_date
, released_time
, CASE WHEN released_date BETWEEN CONVERT(DATETIME, CONVERT(CHAR(8), create_date, 112) + ' ' + CONVERT(CHAR(8), '18:00:00.0000000' , 108)) AND CONVERT(DATETIME, CONVERT(CHAR(8), DATEADD(DAY,1, create_date), 112) + ' ' + CONVERT(CHAR(8), '18:00:00.0000000' , 108)) THEN 'Normal Window'
WHEN released_date BETWEEN CONVERT(DATETIME, CONVERT(CHAR(8), create_date, 112) + ' ' + CONVERT(CHAR(8), '17:59:00.0000000' , 108)) AND CONVERT(DATETIME, CONVERT(CHAR(8), DATEADD(DAY,1, create_date), 112) + ' ' + CONVERT(CHAR(8), '15:00:00.0000000' , 108)) THEN 'Smaller Window'
WHEN released_date BETWEEN CONVERT(DATETIME, CONVERT(CHAR(8), create_date, 112) + ' ' + CONVERT(CHAR(8), '00:00:00.0000000' , 108)) AND CONVERT(DATETIME, CONVERT(CHAR(8), create_date, 112) + ' ' + CONVERT(CHAR(8), '15:00:00.0000000' , 108)) THEN 'Aggreed AUpon'
ELSE 'N/A'
END AS [Dispatched Window]
, Qty
FROM dbo.sales
) AS A

SQL Query needed for desired Output

Below is the sample data which gets filled into sql server database from different PLC machine. datetime,machineID, cycletime(TIME TAKEN TO PRODUCE THAT MATERIAL) AND shift
There are 3 shifts in company A(6:30Am to 2:30PM), B(2:30 to 10:30), C(10:30 to 6:30AM).
when i take C Shift count, my query should take next day data also till 6:30AM time. Where as A shift should take current day data starting from 6:30Am to 2:30Pm.Where as B shift should take current day data starting from 2:31pm to 10:30PM.
Desired Output::
I need to find the quantity for each hour...6:30 to 7:30 what is the quantity... 7:30 to 8:30 what is the quantity and so on for each individual hour. Quantity should not get added with previous hour quantity.. individual hour quantity
You can start with this, and then wrap it with
"SELECT ... just the columns you want
FROM this example
GROUP BY ...."
SELECT
-- isolate Date from Time from HourMinutes only for testing
CONVERT(VARCHAR(10), [MDate], 111) as RealDate
,CONVERT(VARCHAR, [MTime], 108) as RealTime
,SUBSTRING(CONVERT(VARCHAR, [MTime], 108),4,5) as HrMn
-- from midnight to 6:30 adjust to prior day
, Case When (CONVERT(VARCHAR, [MTime], 108) < '06:30:00')
Then CONVERT(VARCHAR(10), DATEADD(day,-1,[MDate]), 111)
Else CONVERT(VARCHAR(10), [MDate], 111)
End as RptDate
-- from after the half hour, report it with the next hour
,Case When (SUBSTRING(CONVERT(VARCHAR, [MTime], 108),1,5)) > '23:30:00'
Then ' 0:30'
When (SUBSTRING(CONVERT(VARCHAR, [MTime], 108),4,5)) > '30:00'
Then STR(DATEPART ( hour , [MTime] ) + 1, 2) + ':30'
Else STR(DATEPART ( hour , [MTime] ), 2) + ':30'
End as RptHour
,[MachinelD]
,[CYCLETIM]
,[Shift]
FROM [StackOver].[dbo].[CShift]
For your add-on question of getting only Previous or Current shift,
we need to think ahead to what the Where clause might look like--
Where (MDate = #fromDate and MTime >= #fromTime)
Or (MDate > #fromDate)
And then, before the main SELECT/FROM, create appropriate local vars --
Declare #fromDate as datetime, #fromTime as datetime
If CONVERT (time, GETDATE()) <= '06:30:00' Begin
Set #fromDate=DATEADD(day,-1,CONVERT (date, GETDATE())) --yesterday
Set #fromTime='14:30'
End
Else If CONVERT (time, GETDATE()) <= '14:30:00' Begin
Set #fromDate=DATEADD(day,-1,CONVERT (date, GETDATE())) --yesterday
Set #fromTime='22:30'
End
Else If CONVERT (time, GETDATE()) <= '22:30:00' Begin
Set #fromDate=CONVERT (date, GETDATE()) --today
Set #fromTime='06:30'
End
Else Begin -- time > 22:30
Set #fromDate=CONVERT (date, GETDATE()) --today
Set #fromTime='14:30'
End
-- for testing only, show the values
Select #fromDate, #fromTime
I leave any remaining question(s) to your own solution

Grouping by DATEPART(hh,TimeStamp) to show nightshift, how do I see data for hours past midnight?

I'm importing my data to excel and so I need to see the date as a varchar to use for a graph in excel but I also need the data for the individual hours in the day as well. My manager wants to see data for the past hour whenever he checks my chart. This is my code so far. Dayshift has been fine but I can't get the hours to go past 24 for nightshift so I can't group them in my graph in excel.
convert(VARCHAR, TimeStamp, 101) as date
,StationID as lane
,DATEPART(hh,TimeStamp)
,.185 as posSD1
,-.185 as negSD1
,.370 as posSD2
,-.370 as negSD2
,.556 as posSD3
,-.556 as negSD3
, COUNT (TrickleActual) as Count
, convert(decimal (18,3) ,AVG (TrickleActual - TrickleTarget)) as Average
FROM CherryBoxInfo
WHERE TimeStamp >= '2015-05-01' -- '2015-05-01 18:30:00'
and TimeStamp between convert(DATETIME, convert(VARCHAR, TimeStamp, 101) + ' ' + '19:00:00') and convert(DATETIME, convert(VARCHAR, DATEADD(day, 1, TimeStamp), 101) + ' ' + '04:30:00')
and (TrickleActual-TrickleTarget) BETWEEN -1 and 1
GROUP BY
convert(VARCHAR, TimeStamp, 101)
,StationID
,DATEPART(hh,TimeStamp)
ORDER BY convert(VARCHAR, TimeStamp, 101)
,StationID
,DATEPART(hh,TimeStamp)
Instead of grouping on datepart, group on date truncated to hour.
Here is how to truncate a date:
select dateadd(hh, datediff(hh, 0, #dt), 0)

DATEDIFF in HH:MM:SS format

I need to calculate the total length in terms of Hours, Minutes, Seconds, and the average length, given some data with start time and end time.
For example the result must be something like 45:15:10 which means 45 hours 15 min 10 sec, or 30:07 for 30 min 07 sec.
We're using SQL Server 2008 R2 and the conversion failed when time is more than 24:59:59. Any idea of how I could do this?
For information, the columns in the table are Id, StartDateTime, EndDateTime, etc. I need to make a monthly report which contains the recordings count of the month, the total length of these records, and the average length. I'd like to know if there is an easy way to perform all of this.
You shouldn't be converting to time - it is meant to store a point in time on a single 24h clock, not a duration or interval (even one that is constrained on its own to < 24 hours, which clearly your data is not). Instead you can take the datediff in the smallest interval required (in your case, seconds), and then perform some math and string manipulation to present it in the output format you need (it might also be preferable to return the seconds to the application or report tool and have it do this work).
DECLARE #d TABLE
(
id INT IDENTITY(1,1),
StartDateTime DATETIME,
EndDateTime DATETIME
);
INSERT #d(StartDateTime, EndDateTime) VALUES
(DATEADD(DAY, -2, GETDATE()), DATEADD(MINUTE, 15, GETDATE())),
(GETDATE() , DATEADD(MINUTE, 22, GETDATE())),
(DATEADD(DAY, -1, GETDATE()), DATEADD(MINUTE, 5, GETDATE())),
(DATEADD(DAY, -4, GETDATE()), DATEADD(SECOND, 14, GETDATE()));
;WITH x AS (SELECT id, StartDateTime, EndDateTime,
d = DATEDIFF(SECOND, StartDateTime, EndDateTime),
a = AVG(DATEDIFF(SECOND, StartDateTime, EndDateTime)) OVER()
FROM #d
)
SELECT id, StartDateTime, EndDateTime,
[delta_HH:MM:SS] = CONVERT(VARCHAR(5), d/60/60)
+ ':' + RIGHT('0' + CONVERT(VARCHAR(2), d/60%60), 2)
+ ':' + RIGHT('0' + CONVERT(VARCHAR(2), d % 60), 2),
[avg_HH:MM:SS] = CONVERT(VARCHAR(5), a/60/60)
+ ':' + RIGHT('0' + CONVERT(VARCHAR(2), a/60%60), 2)
+ ':' + RIGHT('0' + CONVERT(VARCHAR(2), a % 60), 2)
FROM x;
Results:
id StartDateTime EndDateTime delta_HH:MM:SS avg_HH:MM:SS
-- ------------------- ------------------- -------------- ------------
1 2013-01-19 14:24:46 2013-01-21 14:39:46 48:15:00 42:10:33
2 2013-01-21 14:24:46 2013-01-21 14:46:46 0:22:00 42:10:33
3 2013-01-20 14:24:46 2013-01-21 14:29:46 24:05:00 42:10:33
4 2013-01-17 14:24:46 2013-01-21 14:25:00 96:00:14 42:10:33
This isn't precisely what you asked for, as it won't show just MM:SS for deltas < 1 hour. You can adjust that with a simple CASE expression:
;WITH x AS (SELECT id, StartDateTime, EndDateTime,
d = DATEDIFF(SECOND, StartDateTime, EndDateTime),
a = AVG(DATEDIFF(SECOND, StartDateTime, EndDateTime)) OVER()
FROM #d
)
SELECT id, StartDateTime, EndDateTime,
[delta_HH:MM:SS] = CASE WHEN d >= 3600 THEN
CONVERT(VARCHAR(5), d/60/60) + ':' ELSE '' END
+ RIGHT('0' + CONVERT(VARCHAR(2), d/60%60), 2)
+ ':' + RIGHT('0' + CONVERT(VARCHAR(2), d % 60), 2),
[avg_HH:MM:SS] = CASE WHEN a >= 3600 THEN
CONVERT(VARCHAR(5), a/60/60) + ':' ELSE '' END
+ RIGHT('0' + CONVERT(VARCHAR(2), a/60%60), 2)
+ ':' + RIGHT('0' + CONVERT(VARCHAR(2), a % 60), 2)
FROM x;
This query changes the delta column in the 2nd row in the above result from 0:22:00 to 22:00.
I slightly modified Avinash's answer as it may end with error if difference is too big. If you need only HH:mm:ss it is sufficient to distinguish at seconds level ony like this:
SELECT CONVERT(time,
DATEADD(s,
DATEDIFF(s,
'2018-01-07 09:53:00',
'2018-01-07 11:53:01'),
CAST('1900-01-01 00:00:00.0000000' as datetime2)
)
)
SELECT CONVERT(time,
DATEADD(mcs,
DATEDIFF(mcs,
'2007-05-07 09:53:00.0273335',
'2007-05-07 09:53:01.0376635'),
CAST('1900-01-01 00:00:00.0000000' as datetime2)
)
)
If you want to do averages, then the best approach is to convert to seconds or fractions of a day. Day fractions are convenient in SQL Server, because you can do things like:
select avg(cast(endtime - starttime) as float)
from t
You can convert it back to a datetime using the reverse cast:
select cast(avg(cast(endtime - starttime as float) as datetime)
from t
The arithmetic to get the times in the format you want . . . that is a pain. You might consider including days in the final format, and using:
select right(convert(varchar(255), <val>, 120), 10)
To get the hours exceeding 24, here is another approach:
select cast(floor(cast(<val> as float)*24) as varchar(255))+right(convert(varchar(255), <val>, 120), 6)
It uses convert for minutes and seconds, which should be padded with 0s on the left. It then appends the hours as a separate value.
Starting in SQL SERVER 2012, you don't need to use DATEDIFF function. You can use FORMAT function to achieve what you want:
SELECT
FORMAT(CONVERT(TIME, [appoitment].[Start] - [appointment].[End]), N'hh\:mm') AS 'Duration'
FROM
[tblAppointment] (NOLOCK)
A way that avoids overflows and can include days and go all the way to milliseconds in the output:
DECLARE #startDate AS DATETIME = '2018-06-01 14:20:02.100'
DECLARE #endDate AS DATETIME = '2018-06-02 15:23:09.000'
SELECT CAST(DATEDIFF(day,'1900-01-01', #endDate - #startDate) AS VARCHAR) + 'd ' + CONVERT(varchar(22), #endDate - #startDate, 114)
The above will return
1d 01:03:06:900
And, off course, you can use the formatting of your choice
SQL Supports datetime substraction which outputs a new datetime relative to the MIN date (for instance 1900-01-01, you can probably get this value from some system variable) This works better than DATEDIFF, because DATEDIFF will count ONE for each "datepart boundaries crossed", even if the elapsed time is less than a whole datapart. Another nice thing about this method is that it allows you to use the date formatting conversions.
If days is the (positive) number of days, like 0.5 for 12 hours, use this expression to format it as a proper duration:
CONVERT(varchar(9), FLOOR(days * 24)) + RIGHT(CONVERT(char(19), CAST(days AS datetime), 120), 6)
Excel will understands values up to 9999:59:59 when pasted. There apply a custom format: [h]:mm:ss in the English version ([u]:mm:ss for Dutch).

nested select statement with SUM

I would like to know if its possible to write a nested select statment?
I have the following which calculates the booked time:
SELECT description, SUM(ts.booked_time) AS booked_time_total,
CONVERT(VARCHAR(11), #testDate, 106) AS month_name, #week_ref AS week_ref
FROM timesheets ts
WHERE #testDate <= convert(datetime, end_dtm, 120) and
dateadd(wk, 1, #testDate) > convert(datetime, start_dtm, 120)
But the booked time appears to be wrong. Isnt the SUM supposed to calculate the total for each row which are within the start_dtm and end_dtm. So if I have 10 rows with 1 in the booked time you would expect the SUM to be 10.
test data:
SUM calculates the total value of the fields while COUNT is the total number of records.
SELECT description,
COUNT(ts.booked_time) AS booked_time_total,
CONVERT(VARCHAR(11), #testDate, 106) AS month_name,
#week_ref AS week_ref
FROM timesheets ts
WHERE #testDate <= convert(datetime, end_dtm, 120) and
dateadd(wk, 1, #testDate) > convert(datetime, start_dtm, 120)
I think you are looking to use COUNT rather than SUM.