Calculate hours spent in office - sql-server

I have this table structure and some sample data in it. I want to calculate the number of total hours spent by each employee in office at the end of the month but when I run the following query I am getting this error.
Hope someone can help.
CREATE TABLE HOURSSPENT
(
EmpCode INT,
Time_Spent Time(5),
DayDate date
)
INSERT INTO HOURSSPENT VALUES (23, '08:30', '2018-07-12');
INSERT INTO HOURSSPENT VALUES (23, '05:40', '2018-07-13');
INSERT INTO HOURSSPENT VALUES (23, '07:23', '2018-07-16');
INSERT INTO HOURSSPENT VALUES (19, '08:30', '2018-07-12');
INSERT INTO HOURSSPENT VALUES (19, '05:40', '2018-07-13');
INSERT INTO HOURSSPENT VALUES (29, '07:23', '2018-07-12');
SELECT SUM(Time_Spent)
FROM HOURSSPENT
GROUP BY EmpCode
Error:
Msg 8117, Level 16, State 1, Line 1
Operand data type time is invalid for sum operator.

CREATE TABLE HOURSSPENT
(
EmpCode INT,
Time_Spent Time(5),
DayDate date
)
INSERT INTO HOURSSPENT VALUES (23, '08:30', '2018-07-12');
INSERT INTO HOURSSPENT VALUES (23, '05:40', '2018-07-13');
INSERT INTO HOURSSPENT VALUES (23, '07:23', '2018-07-16');
INSERT INTO HOURSSPENT VALUES (19, '08:30', '2018-07-12');
INSERT INTO HOURSSPENT VALUES (19, '05:40', '2018-07-13');
INSERT INTO HOURSSPENT VALUES (29, '07:23', '2018-07-12');
SELECT EMPCODE,CAST(DATEADD(MILLISECOND,SUM(DATEDIFF
(MILLISECOND,0,CAST(TIME_SPENT AS DATETIME))),0) AS TIME) [sum of hrs] FROM HOURSSPENT
GROUP BY EMPCODE
output
EMPCODE sum of hrs
19 14:10:00.0000000
23 21:33:00.0000000
29 07:23:00.0000000

use DATEDIFF() to get the difference in minute and then SUM it up. Divide by 60 to convert back to hour
SELECT EmpCode, SUM(datediff(minute, 0, Time_Spent)) / 60.0
FROM HOURSSPENT
GROUP BY EmpCode
If the total time will not exceed 24 hours, you can just simply do this
SELECT EmpCode,
convert(time, dateadd(minute, SUM(datediff(minute, 0, Time_Spent)), 0))
FROM HOURSSPENT
GROUP BY EmpCode
else, you need to calculate the day, hour, minute separately
day = Total_spent / 60 / 60

Related

How to count number per month and then take average of it in same select statement

How to calculate total average per month in case like below?:
We have 9 claimID's. so Aveage would be 9/ 6 distinct months = 1.5
DECLARE #TestTable TABLE (claimid int, DateClosed datetime)
INSERT INTO #TestTable
VALUES (111, '01-01-2018'), (222, '01-03-2018'), (333, '01-12-2018'),
(444, '07-03-2018'), (555, '08-15-2018'), (666, '09-13-2018'),
(777, '04-03-2019'), (888, '05-01-2019'), (999, '07-01-2018'),
(1000, NULL), (1100, NULL), (1200, NULL)
SELECT
ClaimID,
CAST(DateClosed AS DATE) AS DateClosed,
COUNT(ClaimID) CountClaimID,
COUNT(claimid) OVER (PARTITION BY MONT(DateClosed), YEAR(DateClosed)) AS CountPerMonth
FROM
#TestTable
GROUP BY
ClaimID, DateClosed
Perhaps something like this
Example
SELECT ClaimID
,cast(DateClosed AS date) AS DateClosed
,count(ClaimID) CountClaimID
,count(claimid) OVER ( PARTITION BY Month(DateClosed), year(DateClosed)) AS CountPerMonth
,case when DateClosed is null then 0 else count(DateClosed) over () / (select 0.0+count(distinct left(cast(DateClosed as date),7)) from #TestTable) end AS TotalAverage
FROM #TestTable
GROUP BY ClaimID,DateClosed
Returns

Calculate monthly hours

CREATE TABLE EmpAttendance
(
EmpCode INT,
Time_In Time(5),
Time_Out Time(5),
DayDate date
)
INSERT INTO EmpAttendance VALUES (23, '08:30', '13:00', '2018-07-12');
INSERT INTO EmpAttendance VALUES (23, '13:45', '18:30', '2018-07-12');
INSERT INTO EmpAttendance VALUES (23, '09:15', '12:50', '2018-07-13');
INSERT INTO EmpAttendance VALUES (23, '14:02', '18:22', '2018-07-13');
INSERT INTO EmpAttendance VALUES (23, '08:30', '16:00', '2018-07-14');
INSERT INTO EmpAttendance VALUES (23, '08:45', '17:56', '2018-07-15');
INSERT INTO EmpAttendance VALUES (12, '09:15', '12:50', '2018-07-13');
INSERT INTO EmpAttendance VALUES (12, '14:02', '18:22', '2018-07-13');
INSERT INTO EmpAttendance VALUES (12, '08:30', '16:00', '2018-07-14');
I have this table structure that is storing the time in and out of specific employee on a specific day. I want to calculate the total number of hours that an employee was present for a whole month.
The query should accept 2 input date parameters and employee code and give the output as total hours.
I am able to get daily hours but I want to calculate monthly as well and can't figure out how.
SELECT EmpCode, [DayDate],
FirstIN = CAST(MIN(Time_In) AS TIME),
LastOUT = CAST(MAX(Time_Out) AS TIME),
HoursSpent = DATEDIFF(HOUR, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)),
CONVERT(VARCHAR(6), Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME))/3600)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), (Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)) % 3600) / 60), 2)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)) % 60) , 2 )
AS hoursSpent
FROM EmpAttendance
GROUP BY EmpCode, DayDate
just change it to GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, DayDate), 0)
SELECT EmpCode,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DayDate), 0) as MonthDate,
--FirstIN = CAST(MIN(Time_In) AS TIME),
--LastOUT = CAST(MAX(Time_Out) AS TIME),
HoursSpent = DATEDIFF(HOUR, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)),
CONVERT(VARCHAR(6), Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME))/3600)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), (Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)) % 3600) / 60), 2)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)) % 60) , 2 )
AS HoursSpent
FROM EmpAttendance
-- add the condition here in where clause
WHERE DayDate >= #StartDate
GROUP BY EmpCode, DATEADD(MONTH, DATEDIFF(MONTH, 0, DayDate), 0)
Assuming your Time_In and Time_Out values always relate to the same day, you can just sum up the datediff in minutes for each day:
declare #t table(EmpCode int
,Time_In time(5)
,Time_Out time(5)
,DayDate date
);
insert into #t values(23, '08:30', '13:00', '2018-07-12'),(23, '13:45', '18:30', '2018-07-12'),(23, '09:15', '12:50', '2018-07-13'),(23, '14:02', '18:22', '2018-07-13'),(23, '08:30', '16:00', '2018-07-14'),(23, '08:45', '17:56', '2018-07-15'),(12, '09:15', '12:50', '2018-07-13'),(12, '14:02', '18:22', '2018-07-13'),(12, '08:30', '16:00', '2018-07-14');
select EmpCode
,dateadd(month,datediff(month,0,DayDate),0) as MonthGroup
,sum(datediff(minute,Time_In,Time_Out))/60 as HoursWorked
,sum(datediff(minute,Time_In,Time_Out))%60 as MinutesWorked
from #t
group by EmpCode
,dateadd(month,datediff(month,0,DayDate),0)
;
Output:
+---------+-------------------------+-------------+---------------+
| EmpCode | MonthGroup | HoursWorked | MinutesWorked |
+---------+-------------------------+-------------+---------------+
| 12 | 2018-07-01 00:00:00.000 | 15 | 25 |
| 23 | 2018-07-01 00:00:00.000 | 33 | 51 |
+---------+-------------------------+-------------+---------------+

Sum days between differing date/times progromatically

I have a query that works perfectly fine, by summing the values for a day.
SELECT CAST(fldDateTime AS DATE) AS DayValue, SUM(fldValue) AS Val
FROM [dbo].[Data.tblMeterData]
GROUP BY CAST(fldDateTime AS DATE)
ORDER BY DayValue
The problem I have is that I needed to have the data greater than midnight and up until midnight of the next day. To test I can do this I tested the following code, which helped me deal with the first lot of values that do not have a full day.
DECLARE #a DATETIME
DECLARE #b DATETIME
SET #a = CAST('2016-03-21 00:01:00' AS DATETIME)
SET #b = DATEADD(SECOND,-86399 ,#a)
SELECT #a AS a, #b AS b
SELECT CAST(fldDateTime AS DATE) AS DayValue, SUM(fldValue) AS Val
FROM [dbo].[Data.tblMeterData]
WHERE fldDateTime BETWEEN #b AND #a
GROUP BY CAST(fldDateTime AS DATE)
ORDER BY DayValue
The problem with this is that I do not want to loop through dates, as I have thousands of ID's that I need to process in this way.I cannot include midnight for the first date as the first data is recorded after midnight and the final daily reading is at midnight the following day e.g.
Date > '2016-03-20 00:00:00' AND <= 2016-03-21 00:00:00
How can I do what I need to do the following:
Find the first date for an ID and move up to the first midnight of the following day and repeat this for all following days.
Sum these values so that they are greater than midnight and up to midnight of the next day.
My understanding of the requirements is you are looking for the sum of a value group by meter ID and date, but for each date we also want to include the next day's values. This means each value would count in the sum for its day and the previous day.
Code:
--generate test data
declare #tblMeterData table (
[ID] [int] IDENTITY(1,1) NOT NULL,
[tblMeterData_Id] [int] NOT NULL,
[fldDateTime] [datetime] NOT NULL,
[fldValue] [decimal](18, 2) NULL,
[fldBatchId] [uniqueidentifier] NOT NULL);
insert #tblMeterData (tblMeterData_Id, fldDateTime, fldValue, fldBatchId) values
(18, '2016-12-19 23:59:59', 1.0, newid()),
(18, '2016-12-20 00:00:00', 2.0, newid()),
(18, '2016-12-20 00:30:00', 3.0, newid()),
(18, '2016-12-20 01:00:00', 4.0, newid()),
(18, '2016-12-20 01:30:00', 5.0, newid()),
(18, '2016-12-21 00:00:00', 6.0, newid()),
(18, '2016-12-21 00:30:00', 7.0, newid()),
(18, '2016-12-22 00:00:00', 8.0, newid()),
(18, '2016-12-23 00:00:00', 9.0, newid()),
(19, '2016-12-20 00:00:00', 10.0, newid());
--select * from #tblMeterData order by ID;
--main query
with cte as (
--0:00:00 reports on previous day
select *, cast(dateadd(S, -1, fldDateTime) as date) group_date
from #tblMeterData
union all
--duplicate all records to also group on previous day
select *, cast(dateadd(D, -1, dateadd(S, -1, fldDateTime)) as date) group_date
from #tblMeterData
)
select tblMeterData_Id, group_date, sum(fldValue) sum_value
from cte
group by tblMeterData_Id, group_date
order by tblMeterData_Id, group_date;
Results:
tblMeterData_Id group_date sum_value
18 2016-12-18 3.00
18 2016-12-19 21.00
18 2016-12-20 33.00
18 2016-12-21 24.00
18 2016-12-22 9.00
19 2016-12-18 10.00
19 2016-12-19 10.00
The answer is as follows:
WITH cte AS (
SELECT *, CAST(DATEADD(S, -1, fldDateTime) AS DATE) group_date
FROM [dbo].[Data.tblMeterData]
)
SELECT tblMeterData_Id, group_date, SUM(fldValue) sum_value
FROM cte
GROUP BY tblMeterData_Id, group_date
ORDER BY tblMeterData_Id, group_date;
It works perfectly.

SQL Select - inserting values if not exist from another table grouping by another value

I have two tables
#sales --- where I register the daily number of sales
#o_date --- where I register the opening dates
I need to fill in the date gaps in the #sales table for each available date in #o_dates and this has to be for each code available available in the #sales table
Please, help with this select.
DECLARE #sales TABLE
(
code VARCHAR(10) NOT NULL,
date1 DATETIME NOT NULL,
value NUMERIC(10, 2) NOT NULL
);
INSERT #sales
(
Code,
Date1,
value
)
VALUES ('q', '20140708', 51),
('q', '20140709', 3),
('q', '20140710', 5),
('q', '20140711', 6),
('q', '20140712', 2),
('q', '20140713', 7),
('q', '20140714', 24),
('q', '20140715', 24),
('x', '20140709', 25),
('x', '20140710', 16),
('x', '20140711', 66),
('x', '20140712', 23),
('x', '20140713', 35),
('x', '20140714', 57),
('c', '20140712', 97),
('c', '20140714', 71);
DECLARE #o_dates TABLE
(date2 DATETIME NOT NULL);
INSERT #o_dates
(date2)
VALUES ('20140608'),
('20140707'),
('20140708'),
('20140709'),
('20140710'),
('20140711'),
('20140712'),
('20140713'),
('20140714'),
('20140715'),
('20140716'),
('20140717'),
('20140718'),
('20140719'),
('20140720');
This query gives you the combined sales and dates for each code with the value as null for the missing dates in sales:
select code, date1, value from #sales
union (
select distinct code, date2, null as value from #sales, #o_dates
except
select code, date1, null as value from #sales
group by code, date1
)
order by code, date1, value
This query inserts the dates that are missing in the sales table with 0 as value:
insert #sales
select distinct code, date2, 0 as value from #sales, #o_dates
except
select code, date1, 0 as value from #sales
group by code, date1
I'm sure both queries can be simplified using joins but this was the first solution that I thought of.
Edit:
A join version of the select query above that should be easy to modify to an insert:
select distinct s.code, date2, s2.value from #sales s
cross join #o_dates d
left join #sales s2
on s.code=s2.code and s2.date1=d.date2
order by code, date2

TSQL - Find the 1st or 2nd day of the week

I have a table of stock prices and need to get prices for the 1st day of each week. This SQL in the WHERE clause works well,
DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0)
except when the market is closed on Monday. Labor Day is a good example. I thought using COALESCE would give me the price on Tuesday if one were unavailable for Monday, but this didn't work.
coalesce(DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0), DATEADD(ww, DATEDIFF(ww,0,PriceDt), 1)).
Can someone help with this?
declare #t table (PriceDt datetime, Symbol nvarchar(10), OpenPric float, ClosePrice float)
insert #t values ('2010-08-02 00:00:0.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-09 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-16 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-23 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-30 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-07 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-13 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-20 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-27 00:00:00.000', 'SYM', 15.00, 15.10)
select * from #t
where PriceDt = coalesce(DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0), DATEADD(ww, DATEDIFF(ww,0,PriceDt), 1))
(missing 2010-09-07 00:00:00.000 in the result)
2010-08-02 00:00:00.000 SYM 15 15.1
2010-08-09 00:00:00.000 SYM 15 15.1
2010-08-16 00:00:00.000 SYM 15 15.1
2010-08-23 00:00:00.000 SYM 15 15.1
2010-08-30 00:00:00.000 SYM 15 15.1
2010-09-13 00:00:00.000 SYM 15 15.1
2010-09-20 00:00:00.000 SYM 15 15.1
2010-09-27 00:00:00.000 SYM 15 15.1
This will give you the earliest date that exists in the table for each week (assuming that your week starts on Monday):
select min(Pricedt) Pricedt
from #t
group by DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0)
Now you can just join that result to your table to get the prices for whatever is the first day of the week that has data entered:
select t.Pricedt, t.Symbol, t.OpenPric, t.ClosePrice
from
(
select min(Pricedt) Pricedt
from #t
group by DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0)
) d
join #t t on d.Pricedt = t.PriceDt

Resources