For the answer below, I'm assuming that a record's initial/default status is "Open". I'm also assuming your version of SQL Server has LEAD/LAG functions.
Let's say you have information about 3 records. All 3 records start with a status of "Open".
- For Record 1, the status was changed 4 times:
- It was changed from Open to On Hold at 8:00.
- It was changed from On Hold to Open at 9:00.
- It was changed back to On Hold at 10:00
- It was changed to Closed at 11:00.
- For Record 2, the status was changed twice:
- At 8:00, it was changed from Open to On Hold.
- At 9:00, it was changed to Closed.
- Record 3 just has one change:
- At 8:00, it was changed from Open to On Hold (meaning the current status is On Hold).
Here's the data in a tabular form:
+----+------------+------------+------------------+
| id | old_status | new_status | changed_at |
+----+------------+------------+------------------+
| 1 | Open | On Hold | 2019-03-26 08:00 |
| 1 | On Hold | Open | 2019-03-26 09:00 |
| 1 | Open | On Hold | 2019-03-26 10:00 |
| 1 | On Hold | Closed | 2019-03-26 11:00 |
| 2 | Open | On Hold | 2019-03-26 08:00 |
| 2 | On Hold | Closed | 2019-03-26 09:00 |
| 3 | Open | On Hold | 2019-03-26 08:00 |
+----+------------+------------+------------------+
From the data, and my understanding of your problem, you want the total time a record was on hold. So, for the 3 records above:
- Record 1 was on hold for a total of 2 hours/120 minutes: 1 hour from 8 to 9, then another hour from 10 to 11.
- Record 2 was on hold for just 1 hour: from 8 to 9.
- For Record 3, it's unclear what your expected results are: Would the results be from 8:00 (when it was placed on hold) to the current date/time? Or do you want to exclude this from your results?
To begin attacking the problem, you can first use WINDOW functions to look at related results. I ended up using LAG
.
First, you can use LAG
to figure out where the last change (for the record) occurred:
SELECT
[id],
old_status,
new_status,
changed_at,
prev_changed = LAG(changed_at) OVER
(
PARTITION BY [id]
ORDER BY [id], changed_at
)
FROM audit_records
This gives you the following results:
+----+------------+------------+------------------+------------------+
| id | old_status | new_status | changed_at | prev_changed |
+----+------------+------------+------------------+------------------+
| 1 | Open | On Hold | 2019-03-26 08:00 | NULL |
| 1 | On Hold | Open | 2019-03-26 09:00 | 2019-03-26 08:00 |
| 1 | Open | On Hold | 2019-03-26 10:00 | 2019-03-26 09:00 |
| 1 | On Hold | Closed | 2019-03-26 11:00 | 2019-03-26 10:00 |
| 2 | Open | On Hold | 2019-03-26 08:00 | NULL |
| 2 | On Hold | Closed | 2019-03-26 09:00 | 2019-03-26 08:00 |
| 3 | Open | On Hold | 2019-03-26 08:00 | NULL |
+----+------------+------------+------------------+------------------+
Note the records with a NULL value: These are records that don't have a change before that change. So for Record 1, the change from Open to On Hold is null, since that was the first change.
Now you can wrap this up in a CTE and calculate the number of minutes:
WITH
audit_records_lead_lag([id], old_status, new_status, changed_at, prev_changed) AS
(
SELECT
[id],
old_status,
new_status,
changed_at,
prev_changed = LAG(changed_at) OVER
(
PARTITION BY [id]
ORDER BY [id], changed_at
)
FROM audit_records
)
SELECT
[id],
minutes_in_hold = SUM(DATEDIFF(MINUTE, prev_changed, changed_at))
FROM audit_records_lead_lag
WHERE
old_status = 'On Hold'
AND prev_changed IS NOT NULL
GROUP BY [id]
which gives you the following results:
+----+-----------------+
| id | minutes_in_hold |
+----+-----------------+
| 1 | 120 |
| 2 | 60 |
+----+-----------------+