How to filter unique records depending to some condition by SQL - sql

I have a SQL Server database with a table that gets data from Time and Attendance devices.
It looks like this:
ID
UserID
Date
Time
SignDirection
1988781
25000
2022/01/11
10:02
1
1988782
25000
2022/01/11
10:03
1
1988783
25000
2022/01/11
10:04
1
1988784
25000
2022/01/11
12:30
2
1988785
25000
2022/01/11
12:31
2
1988786
25001
2022/01/11
10:00
1
1988787
25001
2022/01/11
12:30
2
1988788
25002
2022/01/11
10:15
1
1988789
25002
2022/01/11
10:16
1
1988790
25002
2022/01/11
12:19
2
How to filter the data for each User ID to have lower time for SignDirection = 1, and the higher time for SignDirection = 2 ?
To be like :
ID
UserID
Date
Time
SignDirection
1988781
25000
2022/01/11
10:02
1
1988785
25000
2022/01/11
12:31
2
1988786
25001
2022/01/11
10:00
1
1988787
25001
2022/01/11
12:30
2
1988788
25002
2022/01/11
10:15
1
1988790
25002
2022/01/11
12:19
2
I have tried
select distinct
but had no success with that.

The answer given by #LukStorms is good, but it requires another sort for the second row-number.
You can instead use LEAD with the same sort to detect the last row
SELECT
ID,
UserID,
[Date],
[Time],
SignDirection
FROM
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY UserID, SignDirection, [Date] ORDER BY [Time]) rn_asc
, LEAD(Time) OVER (PARTITION BY UserID, SignDirection, [Date] ORDER BY [Time]) nextTime
FROM YourTable t
) q
WHERE ((SignDirection = 1 AND rn_asc = 1) OR
(SignDirection = 2 AND nextTime IS NULL))
ORDER BY
ID,
UserID,
[Date],
[Time],
SignDirection;
db<>fiddle

You can calculate 2 row_number, up & down.
Then filter on them.
SELECT ID, UserID, [Date], [Time], SignDirection
FROM
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY UserID, SignDirection, [Date] ORDER BY [Time] ASC) rn_asc
, ROW_NUMBER() OVER (PARTITION BY UserID, SignDirection, [Date] ORDER BY [Time] DESC) rn_desc
FROM your_time_and_attendance_table t
) q
WHERE ((SignDirection = 1 AND rn_asc = 1) OR
(SignDirection = 2 AND rn_desc = 1))
ORDER BY ID, UserID, [Date], [Time], SignDirection

Related

Find the longest duration during the machine is ON

I have the following table in SQL Server. I would like to find the longest duration for the machine running.
Row
DateTime
Machine On
1
9/22/2022 8:20
1
2
9/22/2022 9:10
0
3
9/22/2022 10:40
1
4
9/22/2022 10:52
0
5
9/22/2022 12:30
1
6
9/22/2022 14:30
0
7
9/22/2022 15:00
1
8
9/22/2022 15:40
0
9
9/22/2022 16:25
1
10
9/22/2022 16:55
0
In the example above, the longest duration for the machine is ON is 2 hours using rows 5 and 6. What would be the best SQL statement that can provide the longest duration given a time range?
Desired Result:
60 minutes
I have looked into the LAG Function and the LEAD Function in SQL.
Here's another way that uses traditional gaps & islands methodology:
WITH src AS
(
SELECT Island, mint = MIN([Timestamp]), maxt = MAX([Timestamp])
FROM
(
SELECT [Timestamp], Island =
ROW_NUMBER() OVER (ORDER BY [Timestamp]) -
ROW_NUMBER() OVER (PARTITION BY Running ORDER BY [Timestamp])
FROM dbo.Machine_Status
) AS x GROUP BY Island
)
SELECT TOP (1) delta =
(DATEDIFF(second, mint, LEAD(mint,1) OVER (ORDER BY island)))
FROM src ORDER BY delta DESC;
Example db<>fiddle based on the sample data in your new duplicate.
If this is really your data, you can simply use INNER JOIN and DATEDIFF:
SELECT MAX(DATEDIFF(MINUTE, T1.[DateTime], T2.[DateTime]))
FROM [my_table] T1
INNER JOIN [my_table] T2
ON T1.[Row] + 1 = T2.[Row];
This is a gaps and islands problem, one option to solve it is to use a running sum that increased by 1 whenever a machine_on = 0, this will define unique groups for consecutive 1s followed by 0.
select top 1 datediff(minute, min([datetime]), max([datetime])) duration
from
(
select *,
sum(case when machine_on = 0 then 1 else 0 end) over (order by datetime desc) grp
from table_name
) T
group by grp
order by datediff(minute, min([datetime]), max([datetime])) desc
See demo
This is a classic Gaps and Islands with a little twist Adj
Example
Select Top 1
Row1 = min(row)
,Row2 = max(row)+1
,TS1 = min(TimeStamp)
,TS2 = dateadd(SECOND,max(Adj),max(TimeStamp))
,Dur = datediff(Second,min(TimeStamp),max(TimeStamp)) + max(Adj)
From (
Select *
,Grp = row_number() over( partition by Running order by TimeStamp) - row_number() over (order by timeStamp)
,Adj = case when Running=1 and lead(Running,1) over (order by timestamp) = 0 then datediff(second,TimeStamp,lead(TimeStamp,1) over (order by TimeStamp) ) else 0 end
From Machine_Status
) A
Where Running=1
Group By Grp
Order By Dur Desc
Results
Row1 Row2 TS1 TS2 Dur
8 12 2023-01-10 08:25:30.000 2023-01-10 08:28:55.000 205

How can I create a "start" "end" time table from a timestamp list

I am trying to create a view that displays the time of employee stamps.
This is what the table looks like now:
Person
Person_Number
Date
Stamp_number
Time_Stamp
Paul
1
22-10-24
1
8:00
Paul
1
22-10-24
2
10:00
Paul
1
22-10-24
3
10:30
Paul
1
22-10-24
4
12:00
Jimmy
2
22-10-23
1
9:00
Jimmy
2
22-10-23
2
11:00
Jimmy
2
22-10-23
3
12:00
And I would like it to look like this using only a select query
Person
Person_Number
Date
Start
End
Duration
Paul
1
22-10-24
8:00
10:00
2:00
Paul
1
22-10-24
10:30
12:00
1:30
Jimmy
2
22-10-23
9:00
11:00
2:00
Jimmy
1
22-10-23
12:00
null
null
Is it possible ?
We can use conditional aggregation along with a ROW_NUMBER trick:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Person_Number, Date
ORDER BY Stamp_number) - 1 rn
FROM yourTable
)
SELECT Person, Person_Number, Date,
MAX(CASE WHEN rn % 2 = 0 THEN Time_Stamp END) AS [Start],
MAX(CASE WHEN rn % 2 = 1 THEN Time_Stamp END) AS [End],
DATEDIFF(MINUTE,
MAX(CASE WHEN rn % 2 = 0 THEN Time_Stamp END),
MAX(CASE WHEN rn % 2 = 1 THEN Time_Stamp END)) AS Duration
FROM cte
GROUP BY Person, Person_Number, Date, rn / 2
ORDER BY 2, 4;
Here is a working demo.
Try the following:
SELECT Person, Person_Number, Date, [Start], [End],
CONVERT(TIME(0), CONVERT(DATETIME, [End]) - CONVERT(DATETIME, [Start])) AS Duration
FROM
(
SELECT Person, Person_Number, Date, MIN(Time_Stamp) AS [Start],
CASE
WHEN MAX(Time_Stamp) <> MIN(Time_Stamp)
THEN MAX(Time_Stamp)
END AS [End] /* To select End as null when there is no End for a Start */
FROM table_name
GROUP BY Person, Person_Number, Date, (Stamp_number+1)/2
) T
ORDER BY Person_Number, Date, [Start]
See a demo.

How to calculate Total working hours of employee by each session

I need to display employee login and logout hours for each session of the day and calculate Total Working hours. If the employee login and logout many times, each session and total should be displayed.
My table will be look like this
Id
EmpId
LocationId
LogDate
AccessType
OutType
1
4545_1
4545
2022-05-25 16:27:41.217
1
NULL
2
4545_1
4545
2022-05-25 17:27:26.673
2
1
4
4545_1
4545
2022-05-25 17:31:30.333
1
NULL
5
4545_1
4545
2022-05-25 19:31:38.973
2
1
6
1212_8
1212
2022-05-26 10:21:38.973
1
NULL
6
1212_8
1212
2022-05-26 12:21:38.973
2
2
Here
AccessType 1=IN 2=OUT
OutType 1=LogOut 2=Session Out
I want the output like this
EmpId
LocationId
SessionStart
SessionEnd
Hours
4545_1
4545
2022-05-25 16:27:41.217
2022-05-25 17:27:26.673
1:00
4545_1
4545
2022-05-25 17:31:30.333
2022-05-25 19:31:38.973
2:00
1212_8
1212
2022-05-26 10:21:38.973
2022-05-26 12:21:38.973
2:00
This is what I tried
select[EmpId],
[LocationId],
ShiftDate,
SessionStartTime,
SessionEndTime
, Total_Time = right(concat('0', Total_Time / 3600), 2) + ':' + right(concat('0', Total_Time % 3600 / 60), 2)
from (
select
[EmpId],[LocationId], ShiftDate = cast(min(LogDate) as date)
, SessionStartTime = min(LogDate)
, SessionEndTime = max(LogDate)
, Total_Time = sum(ss)
from (
select
*, ss = datediff(ss, LogDate, lead(LogDate) over (partition by [EmpId], grp order by LogDate))
from (
select
*, grp = sum(diff) over (partition by [EmpId] order by LogDate)
from (
select
*, diff = iif(datediff(mi, lag(LogDate) over (partition by [EmpId] order by LogDate), LogDate) > 300 and [AccessType] = 1, 1, 0)
from
[tblEmployeeAttendance] where cast(LogDate as date) >= '2022-05-25' and cast(LogDate as date) <= '2022-05-26'
) t
) t
) t
group by [EmpId],[LocationId], grp) t
I got result like this
EmpId
LocationId
SessionStart
SessionEnd
Hours
4545_1
4545
2022-05-25 16:27:41.217
2022-05-25 19:31:38.973
3:00
1212_8
1212
2022-05-26 10:21:38.973
2022-05-26 12:21:38.973
2:00
Here the problem is I get only min login and max logout for the day in one row. But I need each login and logout session of the user on the day and total for each.
I request somebody to help on this.
You can do this with an outer apply.
I don't know what the OutType is used for, what the difference is with AccessType. You did not explain that or any other logic so I just assumed it should work on AccessType.
If that is not the case you can easy adapt the logic in the subquery below.
select e.EmpId,
e.LocationID,
e.logdate as SessionStart,
d.logdate as SessionEnd,
datediff(hour, e.logdate, d.logdate) as Hours
from emp e
outer apply ( select top 1 emp.logdate
from emp
where emp.empid = e.empid
and emp.accesstype = 2
and emp.logdate > e.logdate
order by emp.logdate
) d
where e.accesstype = 1
See the DBFiddle here
Result
EmpId
LocationID
SessionStart
SessionEnd
Hours
4545_1
4545
2022-05-25 16:27:41.217
2022-05-25 17:27:26.673
1
4545_1
4545
2022-05-25 17:31:30.333
2022-05-25 19:31:38.973
2
1212_8
1212
2022-05-26 10:21:38.973
2022-05-26 12:21:38.973
2

Find min and max data column in Table

I have a table that specifies exactly what date and time each employee was in a particular office.
EmployeeTable looks like this:
id
EmployeeID
DateP
TimeP
1
11111
1397/01/02
01:30
2
11111
1398/05/09
05:30
3
11111
1398/06/07
05:10
4
22222
1398/08/09
06:12
5
22222
1399/02/01
07:15
6
11111
1399/07/02
08:51
7
11111
1399/08/06
12:20
8
33333
1399/09/04
20:01
9
33333
1399/12/08
22:05
10
33333
1400/01/01
23:11
11
33333
1400/02/05
14:10
12
22222
1400/04/05
16:25
I want exactly select Min and Max date and time for each Employee when present in a office:
id
EmployeeID
MinDateP
TimeMinDateP
MaxDateP
TimeMaxDateP
1
11111
1397/01/02
01:30
1398/06/07
05:10
2
22222
1398/08/09
06:12
1399/02/01
07:15
3
11111
1399/07/02
08:51
1399/08/06
12:20
4
33333
1399/09/04
20:01
1400/02/05
14:10
5
22222
1400/04/05
16:25
1400/04/05
16:25
My SQL code is:
with tab1 as
(
select *
from EmployeeTable
), tab2 as
(
select
t1.*,
case when lag(t1.EmployeeID) over(order by t1.id) is null then 1
when lag(t1.EmployeeID) over(order by t1.id) = t1.EmployeeID then 0
else 1
end lg
from tab1 t1
)
, tab3 as (
select t1.*,
sum(t1.lg) over(order by t1.id) grp
from tab2 t1
)
select t1.EmployeeID,
min(t1.DateP) as min,
TimeP,
max(t1.DateP)as max,
TimeP
from tab3 t1
group by t1.EmployeeID, t1.grp
But above codes has error.
Can every body help me?
This is a gaps and islands problem. One approach to solve this uses the difference in row numbers method:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY DateP, TimeP) rn1,
ROW_NUMBER() OVER (PARTITION BY EmployeeID ORDER BY DateP, TimeP) rn2
FROM EmployeeTable
),
cte2 AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY EmployeeID, rn1 - rn2
ORDER BY DateP, TimeP) rn_first,
ROW_NUMBER() OVER (PARTITION BY EmployeeID, rn1 - rn2
ORDER BY DateP DESC, TimeP DESC) rn_last
FROM cte
)
SELECT
EmployeeID,
MAX(CASE WHEN rn_first = 1 THEN DateP END) AS MinDateP,
MAX(CASE WHEN rn_first = 1 THEN TimeP END) AS TimeMinDateP,
MAX(CASE WHEN rn_last = 1 THEN DateP END) AS MaxDateP,
MAX(CASE WHEN rn_last = 1 THEN TimeP END ) AS TimeMaxDateP
FROM cte2
GROUP BY
EmployeeID,
rn1 - rn2
ORDER BY
MIN(DateP),
MIN(TimeP);
Note that the logic in the second CTE would be totally unnecessary if you were using a single datetime column to represent the date and time. It is usually not beneficial to separate date and time as you are currently doing.

MS SQL get aggregate datetime difference by status

I have below table in sql.
======================================================
UnitID Status DateTime Value
======================================================
101 A 01/12/2017 00:02:10 10
101 A 01/12/2017 00:02:40 25
101 A 01/12/2017 00:03:20 18
101 B 01/12/2017 00:03:55 30
101 B 01/12/2017 00:04:05 10
101 B 01/12/2017 00:04:30 20
101 B 01/12/2017 00:04:50 10
101 A 01/12/2017 00:05:00 28
101 A 01/12/2017 00:05:50 18
101 A 01/12/2017 00:06:20 18
102 A 01/12/2017 00:02:10 10
102 A 01/12/2017 00:02:40 25
102 A 01/12/2017 00:03:20 18
102 B 01/12/2017 00:03:55 30
102 B 01/12/2017 00:04:05 10
102 B 01/12/2017 00:04:30 20
102 B 01/12/2017 00:04:50 10
102 A 01/12/2017 00:05:00 28
102 A 01/12/2017 00:05:50 18
102 A 01/12/2017 00:06:20 18
From this table i need below mention output.
===========================================
UnitID StatusA StatusB MaxValue
===========================================
101 02:30 00:55 30
102 02:30 00:55 30
what i need is the total time difference by status. so how could i achieve this in mssql query. so here 02:30 is time duration for status "A" in the table.
Thank you in advanced.
As far as I know you cannot have status in different columns, only by row.
SELECT [UnitID], [Status], MAX([DateTime]) - MIN([DateTime]), MAX([Value])
FROM [theTable]
GROUP BY [UnitID], [Status]
Output would be like
101 A 02:30 30
101 B 00:55 30
102 A 02:30 30
102 B 00:55 30
If you have fixed states of A and B you can go messy and do this:
SELECT UnitID, A, B, MaxValue
FROM
(
SELECT [UnitID], MAX([DateTime]) - MIN([DateTime]) AS A, null AS B, MAX([Value]) AS MaxValue
FROM [theTable]
WHERE Status = 'A'
GROUP BY [UnitID]
UNION ALL
SELECT [UnitID], null, MAX([DateTime]) - MIN([DateTime]), MAX([Value])
FROM [theTable]
WHERE Status = 'B'
GROUP BY [UnitID]
) x
You can do what you need with the following query. I tried to separate each step on different CTE's so you can see step by step how to get to your result. LAG will retrieve the previous row value (spliting by the PARTITION BY columns and ordering by the ORDER BY).
;WITH LaggedValues AS
(
SELECT
M.UnitID,
M.Status,
M.DateTime,
LaggedDateTime = LAG(M.DateTime) OVER (PARTITION BY M.UnitID ORDER BY M.DateTime ASC),
LaggedStatus = LAG(M.Status) OVER (PARTITION BY M.UnitID ORDER BY M.DateTime ASC)
FROM
Measures AS M
),
TimeDifferences AS
(
SELECT
T.*,
SecondDifference = CASE
WHEN T.Status = T.LaggedStatus THEN DATEDIFF(SECOND, T.LaggedDateTime, T.DateTime) END
FROM
LaggedValues AS T
),
TotalsByUnitAndStatus AS
(
SELECT
T.UnitID,
T.Status,
SecondDifference = SUM(T.SecondDifference)
FROM
TimeDifferences AS T
GROUP BY
T.UnitID,
T.Status
),
TotalsByUnit AS -- Conditional aggregation (alternative to PIVOT)
(
SELECT
T.UnitID,
StatusA = MAX(CASE WHEN T.Status = 'A' THEN T.SecondDifference END),
StatusB = MAX(CASE WHEN T.Status = 'B' THEN T.SecondDifference END)
FROM
TotalsByUnitAndStatus AS T
GROUP BY
T.UnitID
)
SELECT
T.UnitID,
StatusA = CONVERT(VARCHAR(10), T.StatusA / 60) + ':' + CONVERT(VARCHAR(10), T.StatusA % 60),
StatusB = CONVERT(VARCHAR(10), T.StatusB / 60) + ':' + CONVERT(VARCHAR(10), T.StatusB % 60)
FROM
TotalsByUnit AS T
You can get the difference for each group:
select unitid, status, min(datetime) as mindt, max(datetime) as maxdt, max(value) as maxvalue
from (select t.*,
row_number() over (partition by unitid order by datetime) as seqnum,
row_number() over (partition by unitid, status order by datetime) as seqnum_s
from t
) t
group by unitid, status, (seqnum - seqnum_s);
This solves the "groups-and-islands" problem. Now you can get the information you want using conditional aggregation:
with t as (
select unitid, status, min(datetime) as mindt, max(datetime) as maxdt, max(value) as maxvalue
from (select t.*,
row_number() over (partition by unitid order by datetime) as seqnum,
row_number() over (partition by unitid, status order by datetime) as seqnum_s
from t
) t
group by unitid, status, (seqnum - seqnum_s)
)
select unitid,
sum(case when status = 'A' then datediff(minute, mindt, maxdt) end) as a_minutes,
sum(case when status = 'b' then datediff(minute, mindt, maxdt) end) as a_minutes,
max(maxvalue)
from t
group by unitid;
I'll leave it up to you to convert the minutes back to times.