SQL Server Difference of Aggregate Window - sql

I want to fetch some data from an SQL Server table by creating windows of 15 mins from the start time and find the difference between the MAX and MIN of a certain column (value) in these windows.
The SQL Table stores some telemetry data captured every minute.
Are there any T-SQL functionalities that I can utilize to get such output without running a loop (Windowed Aggregation sort, etc.)?
I could implement this using a loop, but this is very slow as it would take data from every window and add it to a temp table. Below is a dumbed-down snippet of the code:
WHILE(#startTime <#endTimeInput)
BEGIN
INSERT INTO #energyDataTable
SELECT MIN(timeStamp) as startTime, MAX(timeStamp) as endTime, ROUND(MAX (value) - MIN (value),2) as value
FROM PLANT1.DATA_TABLE as ts
WHERE ts.unixTimestamp>=#startTime and ts.unixTimestamp<= #endTime
GROUP BY ts.logicalId
--Increment startTime and endTime to goto next window
SET #startTime = #endTime;
SET #endTime = #startTime + 15 * 60000; --converting 15 mins to millisecs
END
Timestamp
value
unixTime
2021-11-08 00:00:09.000
1527.6
1636329609000
2021-11-08 00:01:09.000
1528.1
1636329669000
2021-11-08 00:02:09.000
1528.6
1636329729000
....
....
....
2021-11-08 00:13:09.000
1534.5
1636330389000
2021-11-08 00:14:09.000
1535.3
1636330479000
2021-11-08 00:15:09.000
1535.5
1636330509000
2021-11-08 00:16:09.000
1536.0
1636330569000
2021-11-08 00:17:09.000
1528.6
1636330629000
....
....
....
2021-11-08 00:28:09.000
1542.5
1636331289000
2021-11-08 00:29:09.000
1543.3
1636331379000
Needs to be transformed to (I don't necessarily need end_time: Added for better understanding):
start_time
end_time
value
2021-11-08 00:00:09.000
2021-11-08 00:14:39.000
7.7
2021-11-08 00:15:09.000
2021-11-08 00:29:39.000
7.8

update
the solution is to get first and last row from 15 mins sections
it can be done like this:
create table #t
(
ts datetime2
,val dec(9,2)
)
insert into #t
values
('2021-11-08 00:00:09.000', 1527.6)
,('2021-11-08 00:01:09.000', 1528.1)
,('2021-11-08 00:02:09.000', 1528.6)
,('2021-11-08 00:13:09.000', 1534.5)
,('2021-11-08 00:14:09.000', 1535.3)
,('2021-11-08 00:15:09.000', 1535.5)
,('2021-11-08 00:16:09.000', 1536.0)
,('2021-11-08 00:17:09.000', 1528.6)
,('2021-11-08 00:28:09.000', 1542.5)
,('2021-11-08 00:29:09.000', 1543.3)
;with
cte_partitions as
(
select *
from
(
select *
,row_number() over(partition by M15.[partition] order by T.ts) as row_asc_id -- first row
,row_number() over(partition by M15.[partition] order by T.ts desc) as row_desc_id -- last row
from #t T
outer apply
(
-- 15 minutes partitions = hours + minutes / 15
select format(ts, 'yyyyMMddHH') + cast(datepart(minute, ts) / 15 as char(1)) as [partition]
) M15
) T
where T.row_asc_id = 1
or T.row_desc_id = 1
)
select PF.ts
,PL.val - PF.val
from cte_partitions PF
inner join cte_partitions PL
on PF.[partition] = pl.[partition]
where PF.row_asc_id = 1
and PL.row_desc_id = 1
Although it's fully inline with your example, it will work only if you have every minute without seconds and so on and so forth.
take a look, please
create table #t
(
ts datetime2
,val int
)
insert into #t
values
('2021-11-09T01:01:00.000Z', 100)
,('2021-11-09T01:02:00.000Z', 102)
,('2021-11-09T01:03:00.000Z', 103)
,('2021-11-09T01:04:00.000Z', 105)
,('2021-11-09T01:05:00.000Z', 107)
,('2021-11-09T01:06:00.000Z', 108)
,('2021-11-09T01:07:00.000Z', 120)
,('2021-11-09T01:08:00.000Z', 123)
,('2021-11-09T01:09:00.000Z', 128)
,('2021-11-09T01:10:00.000Z', 135)
select format(ts, 'hh:mm:ss') + ' - ' + format(next_ts, 'hh:mm:ss')
,cast(next_val - val as varchar(10)) + ' (' + cast(next_val as varchar(10)) + ' - ' + cast(val as varchar(10)) + ')'
from
(
select ts
,val
,lead(ts) over(order by ts) as next_ts
,lead(val) over(order by ts) as next_val
from
(
select *, (row_number() over(order by ts) - 1) % 4 as row_id
from #t
) SQ
where row_id = 0
) SQ
where next_ts is not null

To aggregate them in windows of 5 minutes?
Then this may be as simple as also grouping by the minutes divided by 12.
SELECT
CONCAT(CONVERT(VARCHAR, MIN(t.[Timestamp]), 108), ' - ', CONVERT(VARCHAR, MAX(t.[Timestamp]), 108)) AS Agg_Timestamp,
CONCAT(MAX(t.Energy_Value)-MIN(t.Energy_Value), ' (', MAX(t.Energy_Value), '-', MIN(t.Energy_Value), ')' ) AS Agg_Energy_Value
FROM yourtable t
GROUP BY CAST(t.[Timestamp] AS DATE),
DATEPART(hour, t.[Timestamp]),
ROUND((DATEPART(minute, t.[Timestamp])/12.0), 0)
Result:
Agg_Timestamp | Agg_Energy_Value
:------------------ | :---------------
01:01:00 - 01:05:00 | 7 (107-100)
01:06:00 - 01:10:00 | 27 (135-108)

Related

How to compare the current month value with last 12 months

I'm trying to compare the current month value with the last 12 months values.
E.g:
6/30/2019 100
5/31/2019 90
4/30/2019 80
3/31/2019 70
2/8/2019 60
1/31/2019 50
12/31/2018 40
11/30/2018 30
10/31/2018 20
9/30/2018 10
8/31/2018 90
7/30/2018 110
Now the current month value(6/30/2019) in 100 then I want to compare these value with the last 12 months values. If the current month value is the maximum when compared with the 12 months then I want to set the flag as "max". In the above example, 110 is the maximum but current month 100 i.e., it is minimum when compared with the last 12 months values then I want to set the flag as "min".
Also, I want to get the date also i.e., if it is minimum then what is the maximum value with the date (expected output "MIN(110 as on 7/30/2018)").
Please provide me any solution to achieve this scenario
expected output "MIN(110 as on 7/30/2018)"
If you want to compare the current value to a rolling 12-month min/max, you can use window functions:
select top (1) t.*,
(case when value = max12_value then 'MAX'
when value = min12_value then 'MIN'
end) as flag
from (select t.*,
max(value) over (order by date rows between 11 preceding and current row) as max12_value,
min(value) over (order by date rows between 11 preceding and current row) as min12_value,
from t
) t
order by date desc
If I understand correctly, here's one way to do it:
-- Create a table variable to hold the sample data
DECLARE #data TABLE (
[Date] DATE,
[Value] INT
)
-- Load the sample data table
DECLARE #i INT = 0;
WHILE #i < 12
BEGIN
INSERT INTO #data ([Date], [Value]) values (EOMONTH(DATEADD(M,-#i, GETDATE())), FLOOR(RAND()*(100-1+1))+10);
SET #i = #i + 1;
END;
-- Select the resulting data set with flags
WITH t as
(
SELECT [Date], [Value],
CASE
WHEN [Value] = (SELECT MAX([Value]) FROM #data) THEN 'MAX'
WHEN [Value] = (SELECT MIN([Value]) FROM #data) THEN 'MIN'
ELSE ''
END AS Flag
FROM #data
)
SELECT 'MAX(' + CAST([Value] as VARCHAR(MAX)) + ' as on ' + CAST([Date] as VARCHAR(MAX)) + ')' FROM t WHERE [Flag] = 'MAX'
This will output:
MAX(109 as on 2018-09-30)
Swap "MIN" for "MAX" and you'll have the minimum value.
This following script will give you output considering the current month value always calculated from GETDATE()
WITH CTE (d, value)
AS
(
SELECT id,value FROM your_table
),
CTE2
AS
(
SELECT DISTINCT
(SELECT Value FROM CTE WHERE d>= CAST(DATEADD(DD,-DATEPART(DD,GETDATE()) + 1,GETDATE()) AS DATE)) current_month_value,
MIN(value) min_value,
MAX(value) max_value
FROM CTE
WHERE d < CAST(DATEADD(DD,-DATEPART(DD,GETDATE()) + 1,GETDATE()) AS DATE)
AND d >= CAST(DATEADD(MM,-13,DATEADD(DD,-DATEPART(DD,GETDATE()) + 1,GETDATE()) ) AS DATE)
)
SELECT
CASE
WHEN current_month_value > max_value THEN 'MAX'
ELSE 'MIN(' + CAST(max_value AS VARCHAR)+ ' AS ON '+ (SELECT TOP 1 CAST(d AS VARCHAR) FROM CTE WHERE Value = max_value)+ ')'
END
FROM CTE2

distribute accumulated working hours through days

I have Date time when engine has started working and how long was it working. but sometimes it can work more than 24 Hours.
if it worked for 28 Hours on the starting date i will have record
Name started_working Finished working hours_worked
obj-00123 07/02/2018 13:30 08/02/2018 17:30 28
I need to to have record that will show that engine has worked for 10:30 in 07 and 17:30 in 08.
Name started_working Finished working hours_worked
obj-00123 07/02/2018 13:30 07/02/2018 00:00 10:30
obj-00123 07/02/2018 13:30 08/02/2018 17:30 17:30
or something like that. I don't have any idea how can i get this done. can you give me some clues. i dont ask for writing code if its not too easy.
thank you
This might do the trick for you
--Using CTE to show sample data
;WITH cteX( Name,started_working,Finished_working)
AS
(
SELECT
'obj-00123','07/02/2018 13:30','08/02/2018 17:30' UNION ALL
SELECT 'obj-00155','07/02/2018 15:00','07/02/2018 22:30'
)
SELECT
X.Name
, X.started_working
, X.Finished_working
, HoursWorked = CONVERT(VARCHAR(12), DATEADD(minute, DATEDIFF(minute, X.started_working, X.Finished_working), 0), 114)
FROM
(
SELECT
T1.Name
,T1.started_working
,Finished_working = DATEADD(SECOND,0,DATEADD(DAY, DATEDIFF(DAY,-1,T1.started_working),0)) -- Dummy finish time # Midnight
FROM
cteX T1
WHERE
DATEDIFF(DAY,T1.started_working,T1.Finished_working) <> 0 --Create a dummy finish time #Midnight when start and end not on same day
UNION ALL
SELECT
T2.Name
,started_working = CASE WHEN DATEDIFF(DAY,T2.started_working,T2.Finished_working) <> 0
THEN DATEADD(DAY, DATEDIFF(DAY, 0, T2.Finished_working), 0) --Start # Midnight
ELSE T2.started_working
END
,T2.Finished_working
FROM
cteX T2
) X
ORDER BY
X.Name, X.started_working
OUTPUT
Name started_working Finished_working HoursWorked
obj-00123 2018-07-02 13:30:00.000 2018-07-03 00:00:00.000 10:30:00:000
obj-00123 2018-08-02 00:00:00.000 2018-08-02 17:30:00.000 17:30:00:000
obj-00155 2018-07-02 15:00:00.000 2018-07-02 22:30:00.000 07:30:00:000
According to your sample data working hours may be more than several days. In this case you need to use tally table or recursive CTE. I have used recursive CTE since it's easier to handle result fields. Also there are two columns in result named started_working and started_working2. started_working is from your expected output, but I believe you need started_working2 column
declare #T as table (
Name varchar(100)
, started_working datetime
, finished_working datetime
--, hours_worked int
)
insert into #T
values
('obj-00123', '20180207 13:30', '20180208 17:30')
, ('obj-00123', '20180208 19:00', '20180209 05:00')
, ('obj-00123', '20180209 19:00', '20180209 22:00')
, ('obj-00123', '20180210 19:00', '20180213 22:00')
;with rcte as (
select
*, started_working2 = started_working
, next_date = cast(dateadd(dd, 1, started_working) as date), 1 step
from
#T
union all
select
Name, started_working, finished_working
, cast(next_date as datetime)
, dateadd(dd, 1, next_date), step + 1
from
rcte
where
next_date < finished_working
)
select
Name, started_working, started_working2, finished_working
, right(replace(str(diff / 60), ' ', 0), 2) + ':' + right(replace(str(diff % 60), ' ', 0), 2) hours_worked
from (
select
Name, started_working
, case
when step = 1 then started_working
else started_working2
end started_working2
, case
when step = max(step) over (partition by Name, started_working)
then finished_working else next_date
end finished_working
from
rcte
) t
cross apply (select datediff(mi, started_working2, finished_working) diff) ca
I'd approach the solution something like this:
WITH dynamic_twelths_of_hr_table(datetime2_value) AS
(
SELECT '2017-01-01'
UNION ALL
SELECT DATEADD(MINUTE, 5, datetime2_value)
FROM dynamic_twelths_of_hr_table
WHERE DATEADD(MINUTE, 5, datetime2_value) <= '2019-01-01'
)
,twelths_hr_table AS
(
SELECT
DATEADD(DAY, DATEDIFF(DAY, 0, datetime2_value), 0) AS date_value
,datetime2_value
FROM dynamic_twelths_of_hr_table
)
,modified_source_table AS
(
SELECT
name
,objectid
,engine_start
,ISNULL(engine_stop, GETDATE()) AS engine_stop
,IIF(engine_start IS NULL OR engine_stop IS NULL, 1, 0) AS is_still_running
FROM [YOUR_SOURCE_TABLE]
)
SELECT
name
,objectid
,is_still_running
,date_value
,(COUNT(datetime2_value)/12.0) AS hours_run_on_this_day
FROM
modified_source_table
LEFT JOIN
twelths_hr_table AS tht
ON (tht.datetime2_value BETWEEN engine_start AND engine_stop)
GROUP BY
name, objectid, is_still_running, date_value
ORDER BY
name, objectid, is_still_running, date_value
Note I haven't tested this code so please excuse any small syntax errors.
I've also baked in an assumption about the range of dates to be considered (these can be widened, or made dynamic based on when the query runs), and it has a 5 minute resolution (based on the fact that, at a glance, I could only see one value in the engine_stop column that didn't fall on a 5-minute threshold - so I assume sub-5-minute precision is not required).
Basically what it does is expand each engine row out into 5-minute windows (twelths of an hour), and then simply groups these by day and counts the number of windows per day during which the engine was running.
For currently-running engines, it will calculate how long it has run so far. I trust you can tweak the code to your exact requirements.
thank you to all. this worked perfectly. it needed slight polishing and recursion needed to be set to 0.
But creating view is a trouble with CTE.
create view mroobjectenginerowkinghoursdeclare as
declare #T as table (
Name nvarchar(100)
, OBJECTID varchar(50)
, started_working datetime
,STOPFROM datetime
,STARTDATE datetime
,STOPDATE datetime
,MODIFIEDDATETIME datetime
,START_STOP int
,STARTDESCRIPTION nvarchar(300)
,STOPDESCRIPTION nvarchar(300)
,wattage nvarchar (50)
,purpose nvarchar(300)
,location nvarchar(300)
,finished_working datetime
,oldDiff int
)
insert into #T
select
NAME
,OBJECTID
,STOPTO
,STOPFROM
,STARTDATE
,STOPDATE
,MODIFIEDDATETIME
,START_STOP
,STARTDESCRIPTION
,STOPDESCRIPTION
,wattage
,purpose
,location
,next_stopfrom
,diff
FROM [MicrosoftDynamicsAX].[dbo].[mroobjectengineworkinghours]
;with rcte as (
select
*, started_working2 = started_working
, next_date = cast(dateadd(dd, 1, started_working) as date), 1 step
from
#T
union all
select
Name,OBJECTID, started_working,STOPFROM,STARTDATE,STOPDATE,MODIFIEDDATETIME,START_STOP,STARTDESCRIPTION
,STOPDESCRIPTION,wattage
,purpose
,location, finished_working,oldDiff
, cast(next_date as datetime)
, dateadd(dd, 1, next_date), step + 1
from
rcte
where
next_date < finished_working
)
select
Name,OBJECTID, started_working,STOPFROM,STARTDATE,STOPDATE,MODIFIEDDATETIME,START_STOP,STARTDESCRIPTION
,STOPDESCRIPTION,wattage
,purpose
,location,oldDiff, started_working2, finished_working
, right(replace(str(diff / 60), ' ', 0), 2) + ':' + right(replace(str(diff % 60), ' ', 0), 2) hours_worked
from (
select
Name,OBJECTID, started_working,STOPFROM,STARTDATE,STOPDATE,MODIFIEDDATETIME,START_STOP,STARTDESCRIPTION
,STOPDESCRIPTION,wattage
,purpose
,location,oldDiff
, case
when step = 1 then started_working
else started_working2
end started_working2
, case
when step = max(step) over (partition by Name, started_working)
then finished_working else next_date
end finished_working
from
rcte
) t
cross apply (select datediff(mi, started_working2, finished_working) diff) ca
OPTION (MAXRECURSION 0);

How to display event hour by hour in SQL?

Create table tblEvent ( Event_ID int, Start_Time datetime, End_Time datetime )
insert into tblEvent values(1,'2015-02-10 9:00:00.000','2015-02-10 11:00:00.000')
insert into tblEvent values(2,'2015-02-10 11:00:00.000','2015-02-10 11:20:00.000')
insert into tblEvent values(3,'2015-02-10 11:20:00.000','2015-02-10 13:00:00.000')
and want to be display like below
Hour Event_ID [Start_End]
9 1 9:00-10:00
10 1 10:00-11:00
11 2 11:00-11:20
11 3 11:20-12:00
12 3 12:00-13:00
and we can make the End_Time of Event 3 become 13:30
we had to be display
13 3 13:00-13:30
Can anyone help me?
You can use DATEPART function
DATEPART(HOUR, [Start_End]) AS Hour
select blocks.Hour, e.Event_Id,
format(case when e.Start_Time > blocks.Start_Time then e.Start_Time else blocks.Start_Time end, 'HH:mm') +
'-' +
format(case when e.End_Time < blocks.End_Time then e.End_Time else blocks.End_Time end, 'HH:mm')
from
tblEvent as e inner join
(
select
d0.n + d1.n * 4 as Hour,
dateadd(hh, d0.n + d1.n * 4, cast(cast(current_timestamp as date) as datetime)) as Start_Time,
dateadd(hh, d0.n + d1.n * 4 + 1, cast(cast(current_timestamp as date) as datetime)) as End_Time
from
(select 0 as n union all select 1 union all select 2 union all select 3) as d0,
(select 0 as n union all select 1 union all select 2 union all select 3 union all select 4 union all select 5) as d1
) as blocks
on blocks.End_Time > e.Start_Time and blocks.Start_Time < e.End_Time
order by Event_Id, Hour
Here's a start. SQL Server? Is current day enough? You don't have the format() on SQL 2008 so you'll have to do that part yourself.
I'm not sure this handles all the cases exactly the way you want. You can take the basic idea and extend it across a longer range of hours, say 168 for a full week.
http://sqlfiddle.com/#!6/819c0/9
TRY Some thing like this.This sample data is running ok.
Please provide another sample data atleast 10 rows and don't forget to paste desired output.
Also read my comment in script.
DECLARE #tblEvent TABLE (
Event_ID INT
,Start_Time DATETIME
,End_Time DATETIME
)
INSERT INTO #tblEvent
VALUES (
1
,'2015-02-10 9:00:00.000'
,'2015-02-10 11:00:00.000'
)
,(
2
,'2015-02-10 11:00:00.000'
,'2015-02-10 11:20:00.000'
)
,(
3
,'2015-02-10 11:20:00.000'
,'2015-02-10 13:00:00.000'
);
--select *,DATEdiff(hour,a.Start_Time,a.End_Time) from #tblEvent a
;
WITH CTE
AS (
SELECT *
,ROW_NUMBER() OVER (
ORDER BY Start_Time
) RN
,DATEdiff(hour, Start_Time, End_Time) Diff
FROM #tblEvent
)
--select * from cte
,CTE1
AS (
SELECT Event_ID
,Start_Time
,CASE
WHEN Diff > 1
THEN DATEADD(minute, 60 - DATEPART(minute, Start_Time), Start_Time)
ELSE End_Time
END End_Time
,RN
,DIFF
,1 RN1
,DATEPART(minute, Start_Time) DIFFMIN
FROM CTE
--WHERE RN = 1
UNION ALL
SELECT CASE
WHEN A.Diff > B.DIFF
THEN b.Event_ID
ELSE a.Event_ID
END
,B.End_Time Start_Time
,CASE
WHEN A.Diff > B.DIFF
THEN DATEADD(minute, 60 - DATEPART(minute, B.Start_Time), B.End_Time)
ELSE A.End_Time
END End_Time
,CASE
WHEN A.Diff > B.DIFF
THEN B.RN
ELSE B.RN + 1
END RN
,CASE
WHEN A.Diff > B.DIFF
THEN B.DIFF - 1
ELSE A.Diff
END
,RN1 + 1
,0
FROM CTE1 B
CROSS APPLY (
SELECT *
FROM CTE
WHERE RN = B.RN
) A
WHERE B.DIFF > 0
)
SELECT [Hour]
,Event_ID
,[Start_End]
FROM (
SELECT DATEPART(HOUR, Start_Time) [Hour]
,ROW_NUMBER() OVER (
PARTITION BY Start_Time ORDER BY Start_Time
) RN2
,Event_ID
,CONVERT(VARCHAR(5), Start_Time, 114) + '-' + CONVERT(VARCHAR(5), End_Time, 114) [Start_End]
FROM CTE1
) TBL
WHERE RN2 = 1
--BELOW QUERY RETURN 6 ROWS
-- I AM TRYING TO ELIMINATE THE EXTRA ROWS WITHOUT ROW_NUMBER
--WHICH WOULD BE MORE OPTIMIZE,BUT I AM NOT GETTING WHAT ACTUALLY CAUSING THIS BEHAVIOUR
--MEANWHILE YOU CAN TEST OTHER SAMPLE DATA,AND THROW OTHER SAMPLE DATA
--SELECT DATEPART(HOUR, Start_Time) [Hour]
-- ,Event_ID
-- ,CONVERT(VARCHAR(5), Start_Time, 114) + '-' + CONVERT(VARCHAR(5), End_Time, 114) [Start_End]
-- FROM CTE1

multiple row column values into single row

I have multiple row with different column values sharing the same id .
for e.g
col-A col-B col-C col-D Col-E
1 12 2012-12-01 1900-12-01 2:00:00 1900-12-01 3:30:00
2 12 2012-12-02 1900-12-01 3:00:00 1900-12-01 4:O0:00
I would like to get in single row preferably with separator ( * )
12 2012-12-01 2:00 - 3:30 * 2012-12-02 3:00 - 4:00
To avoid confusion - Edited the separator uses * instead of newline .
It is a pain to eliminate the [col-B] on the second line. The following formats the datetime's the way you seem to want them:
select [col-B],
(convert(varchar(19), [col-C] + [col-d], 121) + ' - ' +
right(convert(varchar(19), [col-E], 121), 8)
) col
from t
As noted by others, this sort of formatting is really not a database issue, but something for your application to handle. That said, and with thanks to Gordon Linoff for the conversion:
declare #TimeRanges as Table
( [col-A] Int Identity, [col-B] Int, [col-C] Date, [col-D] DateTime, [col-E] DateTime );
insert into #TimeRanges ( [col-B], [col-C], [col-D], [col-E] ) values
( 12, '20121201', '19001201 02:00:00', '19001201 03:30:00' ),
( 12, '20121202', '19001201 03:00:00', '19001201 04:00:00' ),
( 13, '20121219', '19001201 09:00:00', '19001201 17:00:00' );
select * from #TimeRanges;
select [col-A],
case when RN = 1 then Cast( [col-B] as VarChar(10) ) else '' end as [col-B], Range
from (
select [col-B], Row_Number() over ( partition by [col-B] order by [col-A] ) as RN,
( Convert( VarChar(19), [col-C] + [col-D], 121 ) + ' - ' +
Right( Convert( VarChar(19), [col-E], 121 ), 8 ) ) as Range
from #TimeRanges ) as ArbitraryPlaceholder
order by [col-A];

How to sum up time field in SQL Server

I have a column called "WrkHrs" and the data type is time(hh:mm:ss). I want to sum up the working hours for employees. But since it's time data type sql server doesn't let me use like sum(columnname).
How can I sum up the time data type fieled in sql query?
SELECT EmployeeID, minutes_worked = SUM(DATEDIFF(MINUTE, '0:00:00', WrkHrs))
FROM dbo.table
-- WHERE ...
GROUP BY EmployeeID;
You can format it pretty on the front end. Or in T-SQL:
;WITH w(e, mw) AS
(
SELECT EmployeeID, SUM(DATEDIFF(MINUTE, '0:00:00', WrkHrs))
FROM dbo.table
-- WHERE ...
GROUP BY EmployeeID
)
SELECT EmployeeID = e,
WrkHrs = RTRIM(mw/60) + ':' + RIGHT('0' + RTRIM(mw%60),2)
FROM w;
However, you're using the wrong data type. TIME is used to indicate a point in time, not an interval or duration. Wouldn't it make sense to store their work hours in two distinct columns, StartTime and EndTime?
In order to sum up the working hours for an employee you can calculate the difference between the shift start time and end time in minutes and convert it to readable format as following:
DECLARE #StartTime datetime = '08:00'
DECLARE #EndTime datetime = '10:47'
DECLARE #durMinutes int
DECLARE #duration nvarchar(5)
SET #durMinutes = DATEDIFF(MINUTE, #StartTime, #EndTime)
SET #duration =
(SELECT RIGHT('00' + CAST((#durMinutes / 60) AS VARCHAR(2)),2) + ':' +
RIGHT('00' + CAST((#durMinutes % 60) AS VARCHAR(2)), 2))
SELECT #duration
The result : 02:47
two hours and 47 minutes
select DATEDIFF(MINUTE, '0:00:00', '00:02:08')
results in :- 2
select DATEDIFF(SECOND, '0:00:00', '00:02:08')
results in :- 128
Using seconds gives a better answer.
So I think the answer can be
SELECT
EmployeeId
, seconds_worked = SUM (DATEDIFF (SECOND, '0:00:00', WrkHrs))
FROM
tbl_employee
GROUP BY
EmployeeId;
DECLARE #Tab TABLE
(
data CHAR(5)
)
INSERT #Tab
SELECT '25:30' UNION ALL
SELECT '31:45' UNION ALL
SELECT '16:00'
SELECT STUFF(CONVERT(CHAR(8), DATEADD(SECOND, theHours + theMinutes,
'19000101'), 8), 1, 2, CAST((theHours + theMinutes) / 3600 AS VARCHAR(12)))
FROM (
SELECT ABS(SUM(CASE CHARINDEX(':', data) WHEN 0 THEN 0 ELSE 3600 *
LEFT(data, CHARINDEX(':', data) - 1) END)) AS theHours,
ABS(SUM(CASE CHARINDEX(':', data) WHEN 0 THEN 0 ELSE 60 *
SUBSTRING(data, CHARINDEX(':', data) + 1, 2) END)) AS theMinutes
FROM #Tab
) AS d
For MS SQL Server, when your WorkingTime is stored as a time, or a varchar in order to sum it up you should consider that:
1) Time format is not supporting sum, so you need to parse it
2) 23:59:59.9999999 is the maximum value for the time.
So, the code that will work to get you the total number of WorkingHours:WorkingMinutes:WorkingSeconds would be the following:
SELECT
CAST(FORMAT((SUM((DATEPART("ss",WorkingTime) + DATEPART("mi",WorkingTime) * 60 + DATEPART("hh",WorkingTime) * 3600)) / 3600),'00') as varchar(max)) + ':' +
CAST(FORMAT((SUM((DATEPART("ss",WorkingTime) + DATEPART("mi",WorkingTime) * 60 + DATEPART("hh",WorkingTime) * 3600)) % 3600 / 60),'00') as varchar(max)) + ':' +
CAST(FORMAT((SUM((DATEPART("ss",WorkingTime) + DATEPART("mi",WorkingTime) * 60 + DATEPART("hh",WorkingTime) * 3600)) % 3600 % 60),'00') as varchar(max)) as WorkingTimeSum
FROM TableName
It must be as simple as that.
Steps
convert time to seconds
sum the RESULT
convert the sum to time
Eg:
take a case you might want to sum the following time:
| present_hours |
|-----------------|
| 00:01:20.000000 |
|-----------------|
| 00:01:13.000000 |
|-----------------|
| 00:01:45.000000 |
|-----------------|
| 00:01:03.000000 |
|-----------------|
| 00:01:10.000000 |
|-----------------|
| 00:00:56.000000 |
SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(present_hours))) as total_present_hours FROM time_booking;