I want to calculate total working hours of an employee for specific day. With my current query it is giving me records for all days but what I want is to get for each day.
Like I have 2 days records so it should show 2 rows but instead it is only returning one.
DECLARE #T TABLE
([EmpID] int, [TimeIn] datetime, [TimeOut] datetime);
INSERT INTO #T
([EmpID], [TimeIn], [TimeOut]) VALUES
(1, '2018-01-10 9:00:00', NULL),
(1, NULL, '2018-01-10 11:00'),
(1, '2018-01-10 11:30:00', NULL),
(1, NULL, '2018-01-10 13:00'),
(1, '2018-01-10 13:30:00', NULL),
(1, NULL, '2018-01-10 18:00'),
(1, '2018-01-11 9:00:00', NULL),
(1, NULL, '2018-01-11 11:00'),
(1, '2018-01-11 11:30:00', NULL),
(1, NULL, '2018-01-11 13:00'),
(1, '2018-01-11 13:30:00', NULL),
(1, NULL, '2018-01-11 18:00')
;
;WITH TM
AS
(
SELECT
EmpID,
MIN([TimeIn]) as StartTime,
MAX([TimeOut]) as EndTime
FROM #T
GROUP BY EmpId
)
SELECT
*,
HoursSpent = DATEDIFF(HOUR, StartTime, EndTime)
FROM TM
What I really want is that by default it should give me yesterday's report or otherwise I could pass value at run time too like I want report of 10th Jan 2018 and something like that but I guess that part is far away as I'm stuck here.
Try:
;WITH cte AS
(
SELECT empid
, MIN(timein) timein
, MAX(timeout) timeout
, cast(coalesce(timein, timeout) AS DATE) d
FROM #T
GROUP BY empid, cast(coalesce(timein, timeout) AS DATE)
)
SELECT empid
, d AS Day
, DateDiff(HOUR, TimeIn, TimeOut) [Hours Worked]
FROM cte
ORDER BY d ASC
;WITH
TM1 as
(
SELECT EmpID,
convert(date, COALESCE(TimeIn, [TimeOut])) as [Date],
TimeIn,
[TimeOut],
grp = (ROW_NUMBER() OVER (PARTITION BY EmpID ORDER BY COALESCE(TimeIn, [TimeOut])) - 1) / 2 + 1
from #T
),
TM2
AS
(
SELECT EmpID,
[Date],
DATEDIFF(HOUR, MIN([TimeIn]), MAX([TimeOut])) as HoursSpent
FROM TM1
GROUP BY EmpID, [Date], grp
)
SELECT EmpID, [Date], SUM(HoursSpent) as HoursSpent
FROM TM2
GROUP BY EmpID, [Date]
/*
RESULT :
EmpID Date HoursSpent
1 2018-01-10 9
1 2018-01-11 9
*/
if you want higher precision, use DATEDIFF(minute) and divide by 60.0
Just Add a Variable to Specify the Date
DECLARE #MyDate DATE
Assign the date to the Variable if you want to get the records for a specific date else keep it NULL.
Now, Change your Code of the Common Table Expression TM as below -- To Add the Where Condition
;WITH TM
AS
(
SELECT
EmpID,
MIN([TimeIn]) as StartTime,
MAX([TimeOut]) as EndTime
FROM #T
WHERE
CAST(ISNULL(#MyDate,GETDATE()-1) AS DATE) = CAST(ISNULL([TimeIn],[TimeOut]) AS DATE)
GROUP BY EmpId
)
SELECT
*,
HoursSpent = DATEDIFF(HOUR, StartTime, EndTime)
FROM TM
When #MyDate = '2018-01-10'
When #MyDate IS NULL
Related
I try to find all Cust who have membership for at least for one day in each month during 2018.
I came up with solution checking their membership at the beginning / middle / end end of each month like in snippet below, but trying to find more intelligent solution.
I know that I can use tally table for each of 365 days to check this but probably there is more elegant solution ? I'm bit new to SQL, I think I'm missing something in GROUPing area.
In the code snippet shown below, both Cust have at least one day membership.
Desired output:
CustID
------
1
22
Code:
with data as
(
select *
from (values (1, 1, '2017-12-11', '2018-01-16'), (1, 22, '2018-01-28', '2018-03-9' ), (1, 333, '2018-03-1', '2018-12-31') , -- island
(22, 1, '2017-12-31', '2018-01-11'), (22, 2, '2017-2-11', '2019-12-31')) as t (CustID, ContractID, StartDD, EndDD) ---
)
select
isdate(startDD), isdate(EndDD)
from
data
), gaps as
(
select
*,
datediff(day, lag(EndDD, 1, StartDD) over (partition by CustID order by StartDD), StartDD) as BreakDD -- negative is island
from
data
)
select
*,
datepart(month,StartDD) mmS , datepart(month,EndDD) mmE
from
gaps
-- and was active any 1+ day during each of the 12 months in 2018 ????
where
1 = 1
/* and (cast('1/1/2018' as date) between StartDD and EndDD
or cast('1/15/2018' as date) between StartDD and EndDD
or cast('1/31/2018' as date) between StartDD and EndDD)
---- etc.. for each month
and ( cast('12/1/2018' as date) between StartDD and EndDD
or cast('12/15/2018' as date) between StartDD and EndDD
or cast('12/31/2018' as date) between StartDD and EndDD
)
*/
--select CustID, max(BreakDD) Max_Days
--from gaps
--group by CustID
Try this answer.
First create a function to return all the month and year between the given dates.
Function:
--SELECT * FROM dbo.Fn_GetMonthYear('2017-12-11','2018-01-16')
ALTER FUNCTION dbo.Fn_GetMonthYear(#StartDate DATETIME,#EndDate DATETIME)
RETURNS TABLE
AS
RETURN(
SELECT DATEPART(MONTH, DATEADD(MONTH, x.number, #StartDate)) AS [Month]
,DATEPART(YEAR, DATEADD(MONTH, x.number, #StartDate)) AS [Year]
FROM master.dbo.spt_values x
WHERE x.type = 'P'
AND x.number <= DATEDIFF(MONTH, #StartDate, #EndDate)
)
Table Schema:
CREATE TABLE #t(CustID INT, ContractID INT, StartDD date, EndDD date)
INSERT INTO #t values (1, 1, '2017-12-11', '2018-01-16'), (1, 22, '2018-01-28', '2018-03-9' ), (1, 333, '2018-03-1', '2018-12-31') , -- island
(22, 1, '2017-12-31', '2018-01-11'), (22, 2, '2017-2-11', '2019-12-31')
Here is the T-SQL Query for your requirement.
SELECT CustID
,COUNT(DISTINCT [Month]) NoOfMonths
FROM(
SELECT *
FROM #t t
CROSS APPLY dbo.Fn_GetMonthYear(StartDD,EndDD)
)D
WHERE [Year] = 2018
GROUP BY CustID
HAVING COUNT(DISTINCT [Month])=12
Result:
CustID NoOfMonths
1 12
22 12
find all Cust who have membership for at least for one day in each
month during 2018
I think this mean that data must be present between '2018-01-01' and '2018-12-31' for each custid.
CREATE TABLE #t(CustID INT, ContractID INT, StartDD date, EndDD date)
INSERT INTO #t values (1, 1, '2017-12-11', '2018-01-16'), (1, 22, '2018-01-28', '2018-03-9' ), (1, 333, '2018-03-1', '2018-12-31') , -- island
(22, 1, '2017-12-31', '2018-01-11'), (22, 2, '2017-2-11', '2019-12-31')
declare #From Datetime='2018-01-01'
declare #To datetime='2018-12-31'
;with CTE as
(
select CustID,min(StartDD)StartDD
,max(EndDD)EndDD
from #t
group by CustID
)
select CustID,StartDD
,EndDD
from CTE
where StartDD<=#From and EndDD>=#To
This script is not tested across all sample data.
But logic is clear.So it can be corrected accordingly.
So tell for what sample data it is not working.
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
I want maximum period of date range that is overlapping each other and if the period is not clashing other date ranges then I want it as it is.
I have this table:
CREATE TABLE [dbo].[table1]
(
[id] [numeric](18, 0) IDENTITY(1,1) NOT NULL,
[StartDate] [datetime] NOT NULL,
[EndDate] [datetime] NOT NULL
)
And their respective values:
INSERT INTO [dbo].[table1]
VALUES (CAST('2013-11-01 00:00:00.000' AS DateTime), CAST('2013-11-10 00:00:00.000' AS DateTime)),
(CAST('2013-11-05 00:00:00.000' AS DateTime), CAST('2013-11-15 00:00:00.000' AS DateTime)),
(CAST('2013-11-10 00:00:00.000' AS DateTime), CAST('2013-11-15 00:00:00.000' AS DateTime)),
(CAST('2013-11-10 00:00:00.000' AS DateTime), CAST('2013-11-25 00:00:00.000' AS DateTime)),
(CAST('2013-11-26 00:00:00.000' AS DateTime), CAST('2013-11-29 00:00:00.000' AS DateTime))
And expected result is:
ID StartDate EndDate
--------------------------------------------------------
1 2013-11-01 00:00:00.000 2013-11-25 00:00:00.000
2 2013-11-26 00:00:00.000 2013-11-29 00:00:00.000
Thanks in advance.
// Edit 1: Thanks.
Works, but there is a new question for breaks in the same table
INSERT INTO [dbo].[table1]
VALUES (CAST('2018-05-03 08:30:00.000' AS DateTime), CAST('2018-05-03 08:45:00.000' AS DateTime)),
(CAST('2018-05-03 08:45:00.000' AS DateTime), CAST('2018-05-03 09:30:00.000' AS DateTime)),
(CAST('2018-05-03 08:45:00.000' AS DateTime), CAST('2018-05-03 11:30:00.000' AS DateTime)),
(CAST('2018-05-03 12:45:00.000' AS DateTime), CAST('2018-05-03 13:00:00.000' AS DateTime)),
(CAST('2018-05-03 14:00:00.000' AS DateTime), CAST('2018-05-03 15:45:00.000' AS DateTime)),
(CAST('2018-05-03 14:15:00.000' AS DateTime), CAST('2018-05-03 15:30:00.000' AS DateTime))
And expected result is:
ID StartDate EndDate
--------------------------------------------------------
1 2018-05-03 08:30:00.000 2018-05-03 11:30:00.000
2 2018-05-03 12:45:00.000 2018-05-03 13:00:00.000
3 2018-05-03 14:00:00.000 2018-05-03 15:45:00.000
Very similar answer, but making use of an index and windowed functions to make the gaps and islands analysis cheaper (faster).
http://sqlfiddle.com/#!18/f19569/3
SELECT
ROW_NUMBER() OVER (ORDER BY MIN(StartDate)),
MIN(StartDate),
MAX(EndDate)
from
(
SELECT
*,
SUM(CASE WHEN PrecedingEndDate >= StartDate THEN 0 ELSE 1 END)
OVER (ORDER BY StartDate, EndDate)
AS GroupID
FROM
(
SELECT
*,
MAX(EndDate)
OVER (ORDER BY StartDate, EndDate
ROWS BETWEEN UNBOUNDED PRECEDING
AND 1 PRECEDING
)
AS PrecedingEndDate
FROM
Table1
)
look_back
)
grouped
GROUP BY
GroupID
This is a form of the gaps and islands problem.
In this case, exists and cumulative sum and group by are the route to the solution:
select row_number() over (order by min(startdate)),
min(startdate), max(enddate)
from (select t1.*, sum(isstart) over (order by startdate) as grp
from (select t1.*,
(case when exists (select 1
from table1 tt1
where tt1.startdate <= t1.enddate and tt1.enddate >= t1.startdate and tt1.id <> t1.id
)
then 0 else 1
end) as isstart
from table1 t1
) t1
) t1
group by grp;
I'm working on a report and I want to display the total time an employee have spent in office for each day. An employee makes in and out of office multiple times so each time the data gets saved.
I have table and records like this.
CREATE TABLE Attendance
(
ID Int,
TimeIn datetime,
TimeOut datetime
);
INSERT INTO Attendance VALUES (1, '2018-01-18 09:37:25.000', '2018-01-18 11:12:25.000');
INSERT INTO Attendance VALUES (1, '2018-01-18 11:21:25.000', '2018-01-18 16:32:25.000');
INSERT INTO Attendance VALUES (1, '2018-01-18 16:37:25.000', '2018-01-18 17:55:25.000');
INSERT INTO Attendance VALUES (2, '2018-01-18 09:56:25.000', '2018-01-18 14:37:25.000');
INSERT INTO Attendance VALUES (2, '2018-01-18 15:00:25.000', '2018-01-18 18:27:25.000');
INSERT INTO Attendance VALUES (1, '2018-01-19 09:12:25.000', '2018-01-19 11:41:25.000');
INSERT INTO Attendance VALUES (1, '2018-01-19 13:23:25.000', '2018-01-19 13:31:25.000');
INSERT INTO Attendance VALUES (2, '2018-01-19 09:12:25.000', '2018-01-19 09:59:25.000');
INSERT INTO Attendance VALUES (2, '2018-01-19 12:55:25.000', '2018-01-19 13:12:25.000');
INSERT INTO Attendance VALUES (2, '2018-01-19 14:01:25.000', '2018-01-19 18:10:25.000');
INSERT INTO Attendance VALUES (1, '2018-01-19 17:31:25.000', '2018-01-19 18:51:25.000');
I want the result to be displayed like this.
ID TimeIn TimeOut TimeIn TimeOut TimeIn TimeOut TimeSpent Day
1 Calculated Hours 2018-01-18
2 Calculated Hours 2018-01-18
1 Calculated Hours 2018-01-19
2 Calculated Hours 2018-01-19
And if possible can we make the TimeIn and TimeOut be displayed as well dynamically ?
Find an employee with maximum number of in/outs in a single day. And change number of columns in the query according to that
select
id, day, TimeIn = max(iif(rn = 1, TimeIn, '')), TimeOut = max(iif(rn = 1, TimeOut, ''))
, TimeIn = max(iif(rn = 2, TimeIn, '')), TimeOut = max(iif(rn = 2, TimeOut, ''))
, TimeIn = max(iif(rn = 3, TimeIn, '')), TimeOut = max(iif(rn = 3, TimeOut, ''))
, TimeIn = max(iif(rn = 4, TimeIn, '')), TimeOut = max(iif(rn = 4, TimeOut, ''))
, TimeIn = max(iif(rn = 5, TimeIn, '')), TimeOut = max(iif(rn = 5, TimeOut, ''))
, TimeSpent = concat(right(concat('0',sum(TimeSpent) / 60),2), ':', right(concat('0',sum(TimeSpent)%60),2))
, Day
from (
select
id, cast(TimeIn as date) day, TimeIn = convert(char(8), TimeIn, 108)
, TimeOut = convert(char(8), TimeOut, 108)
, datediff(mi, TimeIn, TimeOut) TimeSpent
, row_number() over (partition by id, cast(TimeIn as date) order by TimeIn) rn
from
#Attendance
) t
group by id, day
The grouping itself can be done with a regular group by, but perhaps are stuck by grouping on the date? If so, the group by clause can have the same cast/conversions used in the display:
SELECT ID, sum(DATEDIFF(MINUTE, TimeIn,TimeOut)) TotalMinutes,CAST(TimeIn as date) Day
FROM Attendance
group by ID, CAST(TimeIn as date)
Subsequently, formatting of the time can be done with
select ID, format(TotalMinutes / 60, '0') + ':' + format(TotalMinutes % 60, '00'), Day
from(
SELECT ID, sum(DATEDIFF(MINUTE, TimeIn,TimeOut)) TotalMinutes,CAST(TimeIn as date) Day
FROM #Attendance
group by ID, CAST(TimeIn as date)
) a
Hadn't seen the added info on the dynamic columns yet. Will try to dive into that later
edit A possibility for the In/Out columns:
;with a as
(
select *, cast(TimeIn as date) Day ,
format(dateadd(MINUTE, sum( DATEDIFF( MINUTE , TimeIn,TimeOut)) over (partition by ID,cast(TimeIn as date)),0),'H:mm') TimeSpent,
cast(row_number() over (partition by ID, cast(TimeIn as date) order by TimeIn) as varchar) rnr
from Attendance
)
select * from
(select ID, Day, TimeSpent, 'In' + rnr Name, FORMAT(TimeIn, 'H:mm') T from a
union all
select ID, Day, TimeSpent,'Out' +rnr, FORMAT(TimeOut, 'H:mm') from a) u
pivot(min(T) for Name in ([In1], [Out1], [In2], [Out2],[In3],[Out3], [In4], [Out4])) p --as much as desired
Sql Fiddle
I have a table name [EmployeeAttendance] and I have records in it in respective order.
SELECT EI.[FirstName]+' '+EI.[LastName] [EmployeeName], [Dpt].[FullName] [Department], [Desig].[FullName] [Designation], FirstIN = CAST(MIN([AttendanceTimeIn]) AS TIME), LastOUT = CAST(MAX([AttendanceTimeOut]) AS TIME), HoursSpent = DATEDIFF(HOUR, CAST(MIN(AttendanceTimeIn) AS TIME), CAST(MAX(AttendanceTimeOut) AS TIME))
FROM [HRM].[tbl_EmployeeInfo] [EI], [HRM].[tbl_Designation] [Desig], [HRM].[tbl_Department] [Dpt], [HRM].[tbl_EmployeeAttendance] [Attendance]
WHERE [Dpt].[ID] = [EI].[DeptCode] AND [Desig].[ID] = [EI].[DesignationCode] AND [Attendance].[EmpCode] = [EI].[ID] AND [EI].[RecordStatusCode] != '13'
AND CAST([AttendanceTimeIn] as date) = CAST(GetDate()-1 as Date)
GROUP BY
EI.[FirstName]+' '+EI.[LastName], [Dpt].[FullName], [Desig].[FullName], CAST([Attendance].[AttendanceTimeIn] AS DATE)
This is what I am getting as output.
Ajmal John Projects Project Associate 10:16:38.0000000 NULL NULL
Asif Asif Office Staff Office Boy 09:28:36.0000000 NULL NULL
Muhammad Asim Support Database Support Engineer 10:47:28.0000000 19:16:17.0000000 9
Sajjad Ahmed Projects Project Manager 09:41:34.0000000 NULL NULL
Sidra Khizar Quality Assurance SQA Engineer 10:18:48.0000000 NULL NULL
Because I have placed TimeIn and TimeOut in same row it is giving me TimeOut but as for other fields I have Time Out in 2nd row so it is giving Null. Not sure why
If Your Data Type Is TIME, then this works fine
DECLARE #T TABLE
([EmpID] int, [TimeIn] TIME, [TimeOut] TIME)
;
INSERT INTO #T
([EmpID], [TimeIn], [TimeOut])
VALUES
(1, '9:00', NULL),
(1, NULL, '11:00'),
(1, '11:30', NULL),
(1, NULL, '13:00'),
(1, '13:30', NULL),
(1, NULL, '18:00')
;
;WITH TM
AS
(
SELECT
EmpID,
MIN([TimeIn]) as StartTime,
MAX([TimeOut]) as EndTime
FROM #T
GROUP BY EmpId
)
SELECT
*,
HoursSpent = DATEDIFF(HOUR, StartTime, EndTime)
FROM TM
Result
If The DataType is different Try Casting it as Time inside the MAX and MIN Functions
If TimeIn and TimeOut are of TIME datatype then this works
DECLARE #EmployeeAttendance TABLE (EmpID INT, TimeIn TIME, [TimeOut] TIME);
INSERT INTO #EmployeeAttendance
(EmpID, TimeIn, TimeOut)
SELECT 1, '9:00', NULL UNION ALL
SELECT 1, NULL, '11:00' UNION ALL
SELECT 1, '11:30', NULL UNION ALL
SELECT 1, NULL,'13:00' UNION ALL
SELECT 1,'13:30', NULL UNION ALL
SELECT 1,NULL,'18:00';
SELECT
EmpID
, FirstIN = MIN([TimeIn])
, LastOUT = MAX([TimeOut])
, HoursSpent = DATEDIFF(HOUR, MIN(TimeIn), MAX(TimeOut))
--, MAX([TimeOut])-MIN([TimeIn]) AS HoursSpent
FROM
#EmployeeAttendance
GROUP BY
EmpID;
Output
If the columns are DATETIME then try this
DECLARE #EmployeeAttendance TABLE (EmpID INT, TimeIn DATETIME, [TimeOut] DATETIME);
INSERT INTO #EmployeeAttendance
(EmpID, TimeIn, TimeOut)
SELECT 1, '9:00', NULL UNION ALL
SELECT 1, NULL, '11:00' UNION ALL
SELECT 1, '11:30', NULL UNION ALL
SELECT 1, NULL,'13:00' UNION ALL
SELECT 1,'13:30', NULL UNION ALL
SELECT 1,NULL,'18:00';
SELECT
EmpID
, FirstIN = CAST(MIN([TimeIn]) AS TIME)
, LastOUT = CAST(MAX([TimeOut]) AS TIME)
, HoursSpent = DATEDIFF(HOUR, CAST(MIN(TimeIn) AS TIME), CAST(MAX(TimeOut) AS TIME))
--, MAX([TimeOut])-MIN([TimeIn]) AS HoursSpent
FROM
#EmployeeAttendance
GROUP BY
EmpID;
If you have the TimeIn & TimeOut as DateTime field in SQL server then it can be achieved as below:
SELECT
EmpID,
MIN(cast(TimeIN as time)) as FirstIN,
MAX(cast(TimeOut as time)) as LastOUT,
DATEDIFF(hour,MIN([TimeIn]),MAX([TimeOut])) AS HoursSpent
FROM [EmployeeAttendance]
GROUP BY EmpId
Query gives output for above sample data is as follows:
EmpID FirstIN LastOUT HoursSpent
1 09:00:00.0000000 18:00:00.0000000 9