I have a table [EventLog] that contains reads data, recorded by a card reader that controls a gate. However, the same card code [epc] can be read multiple times, during card holder holding for some time near the reader.
I want to show reads for the same code, on the same reader, but ignoring reads for 2 minutes for example.
Example: EventLog
ID EPC ReaderID LogTime
1 1234 1 2016-04-15 12:33:55
2 1234 1 2016-04-15 12:34:05
3 1234 1 2016-04-15 12:34:10
4 4321 2 2016-04-15 12:34:12
5 4321 2 2016-04-15 12:34:14
Desired result:
ID EPC ReaderID LogTime
1 1234 1 2016-04-15 12:33:55
4 4321 2 2016-04-15 12:34:12
What I am using now is the windows function LAG to determine the difference in minutes between each read and it previous one:
SELECT EPC, ReaderName, PersonName, LogTime
FROM (
SELECT EPC, ReaderName, PersonName, LogTime,
DATEDIFF(MINUTE, LAG(LogTime) OVER (PARTITION BY EPC, ReaderID ORDER BY LogTime), LogTime) diff_prev
FROM EventLog l
LEFT OUTER JOIN Person p ON p.EPC = l.EPC
INNER JOIN Reader r ON r.ID = l.ReaderID
) tbl
WHERE diff_prev IS NULL OR diff_prev >= #ignoreMinutes
ORDER BY LogTime
Where #ignoreMinutes is a parameter that specifies how many minutes to ignore the same read.
But this solution is not correct in cases where the card is read once per second, for 3 hours. for Example:
ID EPC ReaderID LogTime diff_prev
1 1234 1 2016-04-15 12:33:55 NULL
2 1234 1 2016-04-15 12:34:05 0
3 1234 1 2016-04-15 12:34:10 0
4 1234 1 2016-04-15 12:34:32 0
5 1234 1 2016-04-15 12:34:54 0
6 1234 1 2016-04-15 12:35:14 0
7 1234 1 2016-04-15 12:35:34 0
8 1234 1 2016-04-15 12:35:54 0
9 1234 1 2016-04-15 12:36:04 0
10 1234 1 2016-04-15 12:36:15 0
11 4321 2 2016-04-15 12:44:12 NULL
12 4321 2 2016-04-15 12:44:14 0
As you see, my solution when executed with #ignoreMinutes = 1, will result in only 2 rows selected ID = 1, 11 since the rest are all diff_prev = 0. But the correct result set should be ID = 1, 6, 10, 11
Can you help? Thanks!
Here's a 'candidate' solution I came up with. At least it works correctly on your last example, returning records 1, 6, 10, 11.
DECLARE #intervalSeconds INT
SET #intervalSeconds = 60;
WITH EL AS
(
-- Select first record for each EPC, this is the baseline for recursion
SELECT
ID,
EPC,
LogTime
FROM EventLog
WHERE LogTime = (SELECT MIN(LogTime) FROM EventLog IEL WHERE IEL.EPC = EventLog.EPC)
-- Add following events
UNION ALL
SELECT
ID,
EPC,
LogTime
FROM
(
SELECT
NextEvent.ID,
NextEvent.EPC,
NextEvent.LogTime,
ROW_NUMBER() OVER(PARTITION BY NextEvent.EPC ORDER BY NextEvent.LogTime) eventNumber
FROM EventLog NextEvent
JOIN
(
SELECT
ID,
ROW_NUMBER() OVER(PARTITION BY EPC ORDER BY LogTime DESC) eventNumber, -- Reverse numbering to get last row by readNumber = 1
EPC,
LogTime
FROM EL -- Recursion
) PreviousEvent -- Here we have all already selected events wich we're interested in
ON PreviousEvent.EPC = NextEvent.EPC
AND PreviousEvent.eventNumber = 1 -- We need only the last one for each EPC
WHERE DATEDIFF(SECOND, PreviousEvent.LogTime, NextEvent.LogTime) > #intervalSeconds
) NextCandidateEvents -- Here we have all events with desired interval offset for each EPC
WHERE NextCandidateEvents.eventNumber = 1 -- We need only the first one for each EPC
)
SELECT * FROM EL
ORDER BY EPC, LogTime
Related
Following is my table and sample data
DECLARE #Employee_Log table(ID int,eid int, ecode varchar(100), emp_startdate date)
INSERT INTO #Employee_Log
SELECT 1, 1, 'aaa','2019-01-01'
UNION ALL
SELECT 2, 1, 'aaa','2019-01-05'
UNION ALL
SELECT 3, 1, 'bbb','2019-01-03'
UNION ALL
SELECT 4, 2, 'aaa','2019-01-03'
UNION ALL
SELECT 5, 1, 'aaa','2019-02-01'
UNION ALL
SELECT 6, 1, 'aaa','2019-02-15'
UNION ALL
SELECT 7, 1, 'aaa','2019-02-19'
UNION ALL
SELECT 8, 1, 'aaa','2019-02-28'
In the above data I want to remove the duplication based on eid and ecode .If the emp_startdate are within 7 days then take the latest data and ignore the rest data.
I tried the following code but how to add the condition check for week range
SELECT
ROW_NUMBER() OVER(PARTITION BY eid,ecode ORDER BY emp_startdate desc) as rownum,
ID,eid,ecode,emp_startdate
FROM #Employee_Log
I want the result as shown below
ID eid ecode emp_startdate
2 1 aaa 2019-01-05
5 1 aaa 2019-02-01
4 2 aaa 2019-01-03
7 1 aaa 2019-02-19
8 1 aaa 2019-02-28
3 1 bbb 2019-01-03
I am still not sure what you want to happen if more than 2 events happen in the same 7 days. But this solution will get the latest date of all series of dates where the difference between dates is 7 days or less.
select ID,eid,ecode,emp_startdate
from
(
select ID,
eid,
ecode,
emp_startdate,
datediff(day
,emp_startdate
,lead(emp_startdate)
over
(partition by eid,ecode order by emp_startdate)) l
from #Employee_Log
) a
where l is null or l>7
ID eid ecode emp_startdate
-- --- ----- -------------
3 1 bbb 2019-01-03
2 1 aaa 2019-01-05
5 1 aaa 2019-02-01
7 1 aaa 2019-02-19
8 1 aaa 2019-02-28
4 2 aaa 2019-01-03
The following query will give you what you have asked to get in plain English in your question but your sample data and desired output contradicts your own question:
SELECT *
FROM
(
SELECT
ROW_NUMBER() OVER (PARTITION BY eid , ecode , YEAR(emp_startdate)
, DATEPART(WEEK, emp_startdate)
ORDER BY emp_startdate DESC
) AS rownum
, ID
, eid
, ecode
, emp_startdate
FROM #Employee_Log
) x
WHERE x.rownum = 1;
I am trying to think of a way on a SQL Server 2008 database to run through a sales order table and get open demand for a part, order it by due date, then look at a purchase order table and fulfill the sales orders by PO, ordering the PO supply by due date as well. At the same time, I need to show what PO(s) are fulfilling the sales order.
For example:
SO table
SO# DueDate Part Number Required QTY
---------------------------------------------
100 9/3/16 1012 2
101 9/12/16 1012 1
107 10/11/16 1012 4
103 10/17/16 1012 7
PO table:
PO# DueDate Part Number Ordered QTY
--------------------------------------------
331 9/1/16 1012 1
362 9/2/16 1012 1
359 9/24/16 1012 5
371 10/1/16 1012 3
380 10/10/16 1012 10
With this data, I would like to see this result:
SO# DueDate Part Number Required QTY PO number QTY Used QTY Remain
--------------------------------------------------------------------------
100 9/3/16 1012 2 331 1 0
100 9/3/16 1012 1 362 1 0
101 9/12/16 1012 1 359 1 4
107 10/11/16 1012 4 359 4 0
103 10/17/16 1012 7 371 3 0
103 10/17/16 1012 7 380 4 6
I have done this sales order fulfillment process before, but not to the point of breaking down what PO(s) are fulfilling the order, only to the point of summing all open supply, then running through and subtracting the supply from each sales order to get a running balance of supply left.
Many thanks in advance for your help.
I found a bit weird solution, hope it helps you. Maybe later I could optimize it, but now I post it as is:
;WITH cte AS (
SELECT 1 as l
UNION ALL
SELECT l+1
FROM cte
WHERE l <= 1000000
), SO_cte AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY DueDate ASC) as rn
FROM SO s
CROSS JOIN cte c
WHERE c.l <= s.[Required QTY]
), PO_cte AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY DueDate ASC) as rn
FROM PO p
CROSS JOIN cte c
WHERE c.l <= p.[Ordered QTY]
), almost_done AS (
SELECT DISTINCT
s.SO#,
s.DueDate,
s.[Part Number],
p.PO#,
s.[Required QTY],
p.[Ordered QTY]
FROM SO_cte s
LEFT JOIN PO_cte p
ON p.rn = s.rn
), final AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY DueDate) AS RN
FROM almost_done
)
SELECT f.SO#,
f.DueDate,
f.[Part Number],
f.[Required QTY],
f.PO#,
CASE WHEN f.[Ordered QTY]>f.[Required QTY]
THEN ISNULL(ABS(f1.[Required QTY]-f1.[Ordered QTY]),f.[Required QTY])
ELSE f.[Ordered QTY] END
as [QTY Used],
f.[Ordered QTY] -
CASE WHEN f1.PO# = f.PO#
THEN f1.[Ordered QTY]
ELSE
CASE WHEN f.[Ordered QTY]>f.[Required QTY]
THEN ISNULL(ABS(f1.[Required QTY]-f1.[Ordered QTY]),f.[Required QTY])
ELSE f.[Ordered QTY] END
END as [QTY Remain]
FROM final f
LEFT JOIN final f1
ON f.RN = f1.RN+ 1
AND (f.SO# = f1.SO# OR f.PO# = f1.PO#)
OPTION(MAXRECURSION 0)
Output for data you provided:
SO# DueDate Part Number Required QTY PO# QTY Used QTY Remain
100 2016-09-03 1012 2 331 1 0
100 2016-09-03 1012 2 362 1 0
101 2016-09-12 1012 1 359 1 4
107 2016-10-11 1012 4 359 4 0
103 2016-10-17 1012 7 371 3 0
103 2016-10-17 1012 7 380 4 6
Let's assume I have a table which has columns From and To which are dates and a bit type column which identifies whether it is a cancel (1 = cancel). Also an Id which is a PK and CancelId which references what is cancelled.
Let's say I have records which look like:
Id From To IsCancel CancelId
1 2015-01-01 2015-01-31 0 NULL
2 2015-01-03 2015-01-09 1 1
3 2015-01-27 2015-01-31 1 1
I am expecting the result to show what intervals of then non-cancel records are still uncancelled:
Id From To
1 2015-01-01 2015-01-02
1 2015-01-10 2015-01-26
I can make it so it would split each record into dates, then subtract cancelled dates from the records then merge the intervals but since I have quite a lot of records, I find this very inefficient and am pretty sure that I am overlooking something simple.
The task you want to achieve is non trivial. A possible solution involves placing all From / To dates in an ordered sequence. The following UNPIVOT operation:
SELECT ID, EventDate, StartStop,
ROW_NUMBER() OVER (ORDER BY ID, EventDate, StartStop) AS EventRowNum,
IsCancel
FROM
(SELECT ID, IsCancel, [From], [To]
FROM Event) Src
UNPIVOT (
EventDate FOR StartStop IN ([From], [To])
) AS Unpvt
produces this result set:
ID EventDate StartStop EventRowNum IsCancel
--------------------------------------------------
1 2015-01-01 From 1 0
2 2015-01-03 From 2 1
2 2015-01-09 To 3 1
3 2015-01-27 From 4 1
3 2015-01-31 To 5 1
1 2015-01-31 To 6 0
Using a CTE, you can subsequently simulate LEAD function (available from SQL Server 2012 onwards) in order to place in a single record the current and the next date from the sequence above:
;WITH StretchEventDates AS
(
-- above query goes here
), CTE AS
(
SELECT s.ID, s.EventDate, s.StartStop, s.IsCancel,
sLead.EventDate As LeadEventDate, sLead.StartStop AS LeadStartStop, sLead.IsCancel AS LeadIsCancel
FROM StretchEventDates AS s
LEFT JOIN StretchEventDates AS sLead ON s.EventRowNum + 1 = sLead.EventRowNum
)
The above produces the following result set:
ID EventDate StartStop IsCancel LeadEventDate LeadStartStop LeadIsCancel
--------------------------------------------------------------------------------------
1 2015-01-01 From 0 2015-01-03 From 1
2 2015-01-03 From 1 2015-01-09 To 1
2 2015-01-09 To 1 2015-01-27 From 1
3 2015-01-27 From 1 2015-01-31 To 1
3 2015-01-31 To 1 2015-01-31 To 0
1 2015-01-31 To 0 NULL NULL NULL
Using CASE statements you can filter these records in order to get the desired output.
Putting it all together:
;WITH StretchEventDates AS
(
SELECT ID, EventDate, StartStop,
ROW_NUMBER() OVER (ORDER BY EventDate, StartStop) AS EventRowNum,
IsCancel
FROM
(SELECT ID, IsCancel, [From], [To]
FROM Event) Src
UNPIVOT (
EventDate FOR StartStop IN ([From], [To])
) AS Unpvt
), CTE AS
(
SELECT s.ID, s.EventDate, s.StartStop, s.IsCancel,
sLead.EventDate As LeadEventDate, sLead.StartStop AS LeadStartStop, sLead.IsCancel AS LeadIsCancel
FROM StretchEventDates AS s
LEFT JOIN StretchEventDates AS sLead ON s.EventRowNum + 1 = sLead.EventRowNum
), CTE_FINAL AS
(SELECT *,
CASE WHEN StartStop = 'From' AND IsCancel = 0 THEN EventDate
WHEN StartStop = 'To' AND IsCancel = 1 THEN DATEADD(d, 1, EventDate)
END AS [From],
CASE WHEN LeadStartStop = 'From' AND LeadIsCancel = 1 THEN DATEADD(d, -1, LeadEventDate)
WHEN LeadStartStop = 'To' AND LeadIsCancel = 0 THEN LeadEventDate
END AS [To]
FROM CTE
)
SELECT ID, [From], [To]
FROM CTE_FINAL
WHERE [From] IS NOT NULL AND [To] IS NOT NULL AND [From] <= [To]
You may have to add additional CASEs in the query above to handle additional combinations of 'cancelations' following 'non-canceled' (and vice-versa) events.
With the data provided in the OP the above yields the following output:
ID From To
---------------------------
1 2015-01-01 2015-01-02
2 2015-01-10 2015-01-26
I have a data like this in the database
ID Server DownTime ServerStatus
--- ----------------------- ------------
1 2012-03-30 00:00:00.000 1
2 2012-03-30 00:30:00.000 0
3 2012-03-30 01:00:00.000 0
4 2012-03-30 01:30:00.000 0
5 2012-03-30 02:00:00.000 1
6 2012-03-30 02:30:00.000 1
7 2012-03-30 03:00:00.000 0
8 2012-03-30 03:30:00.000 1
I need a query or stored procedure that will give me output as
Start Time EndTime TotalDownTimeinMinutes
------------ ------------ ----------------------
3/30/12 0:30 3/30/12 2:00 90
3/30/12 3:00 3/30/12 3:30 30
-- because each "back up" can relate to multiple "down" times,
-- we take the longest period using MIN
SELECT Min(ServerDownTime) StartTime,
UpTime EndTime,
DateDiff(MI, Min(ServerDownTime), UpTime)
FROM
(
SELECT Down.ServerDownTime,
(-- subquery gives you the time when it came back up
SELECT Top 1 Up.ServerDownTime
FROM Tbl Up
WHERE Up.ServerDownTime > Down.ServerDownTime
AND Up.ServerStatus=1
ORDER BY Up.ServerDownTime ASC) UpTime
FROM Tbl Down
WHERE Down.ServerStatus=0 -- find all the downs
) X
GROUP BY UpTime
ORDER BY UpTime
You can test the above query using this DDL
create table Tbl
(
ID int,
ServerDownTime datetime,
ServerStatus bit
)
insert Tbl select
1 ,'2012-03-30 00:00:00.000', 1 union all select
2 ,'2012-03-30 00:30:00.000', 0 union all select
3 ,'2012-03-30 01:00:00.000', 0 union all select
4 ,'2012-03-30 01:30:00.000', 0 union all select
5 ,'2012-03-30 02:00:00.000', 1 union all select
6 ,'2012-03-30 02:30:00.000', 1 union all select
7 ,'2012-03-30 03:00:00.000', 0 union all select
8 ,'2012-03-30 03:30:00.000', 1
Or if you're on the web and nowhere near a SQL Server, here's an SQL Fiddle
This solution is based on recursive CTE's:
DECLARE #MyTable TABLE (
ID INT PRIMARY KEY,
ServerDownTime DATETIME NOT NULL,
UNIQUE (ServerDownTime),
ServerStatus BIT NOT NULL
);
INSERT #MyTable (ID, ServerDownTime, ServerStatus)
SELECT 1,'2012-03-30T00:00:00',1 UNION ALL
SELECT 2,'2012-03-30T00:30:00',0 UNION ALL
SELECT 3,'2012-03-30T01:00:00',0 UNION ALL
SELECT 4,'2012-03-30T01:30:00',0 UNION ALL
SELECT 5,'2012-03-30T02:00:00',1 UNION ALL
SELECT 6,'2012-03-30T02:30:00',1 UNION ALL
SELECT 7,'2012-03-30T03:00:00',0 UNION ALL
SELECT 8,'2012-03-30T03:30:00',1;
WITH Base
AS
(
SELECT *, ROW_NUMBER() OVER(ORDER BY t.ServerDownTime) AS RowNum
FROM #MyTable t
), DownTimeGrouping
AS
(
SELECT crt.RowNum,
crt.ID,
crt.ServerDownTime,
crt.ServerStatus,
CASE WHEN crt.ServerStatus=0 THEN 1 END AS GroupID,
CASE WHEN crt.ServerStatus=0 THEN 1 ELSE 0 END AS LastGroupID
FROM Base crt
WHERE crt.RowNum=1
UNION ALL
SELECT crt.RowNum,
crt.ID,
crt.ServerDownTime,
crt.ServerStatus,
CASE
WHEN prev.ServerStatus=0 AND crt.ServerStatus IN(0,1) THEN prev.GroupID
WHEN prev.ServerStatus=1 AND crt.ServerStatus=0 THEN prev.LastGroupID+1
END AS GroupID,
CASE
WHEN prev.ServerStatus=0 AND crt.ServerStatus IN(0,1) THEN prev.GroupID
WHEN prev.ServerStatus=1 AND crt.ServerStatus=0 THEN prev.LastGroupID+1
WHEN prev.ServerStatus=1 AND crt.ServerStatus=1 THEN prev.GroupID
END AS LastGroupID
FROM Base crt
INNER JOIN DownTimeGrouping prev ON crt.RowNum=prev.RowNum+1
)
SELECT *, DATEDIFF(MINUTE,x.StartTime,x.EndTime) AS MinutesDiff
FROM (
SELECT t.GroupID, MIN(t.ServerDownTime) AS StartTime, MAX(t.ServerDownTime) AS EndTime
FROM DownTimeGrouping t
WHERE t.GroupID IS NOT NULL
GROUP BY t.GroupID
) x
The basic idea is to group the rows starting with a ServerStatus=0 row and ending with a ServerStatus=1 row. For example, if you run this query you will see the downtime groups (column GroupID)::
WITH Base
AS
(...), DownTimeGrouping
AS
(...)
SELECT *
FROM DownTimeGrouping g
ORDER BY g.RowNum
RowNum ID ServerDownTime ServerStatus GroupID LastGroupID
-------------------- ----------- ----------------------- ------------ ----------- -----------
1 1 2012-03-30 00:00:00.000 1 NULL 0
2 2 2012-03-30 00:30:00.000 0 1 1
3 3 2012-03-30 01:00:00.000 0 1 1
4 4 2012-03-30 01:30:00.000 0 1 1
5 5 2012-03-30 02:00:00.000 1 1 1
6 6 2012-03-30 02:30:00.000 1 NULL 1
7 7 2012-03-30 03:00:00.000 0 2 2
8 8 2012-03-30 03:30:00.000 1 2 2
In SQL Server, I have a table (let's call it TransList) that looks like:
TPN Start End TDate DoneBy
10 6 7 2003-03-17 14:48:42.750 User2 *
10 1 6 2003-03-13 08:02:09.317 User3
11 3 6 2003-03-21 08:15:45.410 User3 ** yes
11 6 3 2003-03-13 08:13:13.920 User4 <--
11 5 6 2003-03-08 17:39:51.460 User4
12 13 3 2003-03-19 10:58:23.187 User8 *
12 6 13 2003-03-17 14:48:42.750 User7
12 3 6 2003-03-13 08:02:09.317 User6
12 1 3 2003-03-01 14:09:17.167 User1
13 3 6 2003-03-19 10:58:23.187 User1 *** no
13 1 3 2003-03-01 14:09:17.167 User2 <--
14 3 6 2003-03-21 08:15:45.410 User5 ** yes
14 13 3 2003-03-13 08:13:13.920 User6 <--
14 6 13 2003-03-08 17:39:51.460 User7
15 6 3 2003-03-17 14:48:42.750 User2 *
15 1 6 2003-03-13 08:02:09.317 User3
This is the result of a pretty complex query which joins two separate SELECT statements which joins several tables each. Rows are ordered by TPN ASC, TDate DESC.
I would now like to filter this table and obtain:
TPN Start End TDate DoneBy
10 6 7 2003-03-17 14:48:42.750 User2
11 3 6 2003-03-21 08:15:45.410 User3
12 13 3 2003-03-19 10:58:23.187 User8
14 3 6 2003-03-21 08:15:45.410 User5
15 6 3 2003-03-17 14:48:42.750 User2
That is:
the newest transaction for each selected TPN
TPN is selected based on conditions on its newest transaction and/or its two newest transactions
Rows marked with * are there because (Start=6 and End=7) or (Start=13 and End=3) or (Start=6 and End=3) therefore I do not care of other transactions for these TPN
Rows marked with ** are there because (Start=3 and End=6) and for previous transaction (Start=6 and End=3)
Rows marked with *** are not there because (Start=3 and End=6) but for previous transaction is not (Start=6 and End=3)
I might need to further select based on which User did the previous transaction and/or have more complex logical conditions for * and ** (the state machine is complex and I have not yet finished checking it) but *** is always a not **.
I always need to check only the top 2 transactions for each TPN.
I am new to SQL and I spent already a couple of days to try figure out how I can achieve this. I considered self joining TransList, using LIMIT or TOP or walking through the table but I did not manage to make any of this solution work.
Can anyone help?
Edit:
The table above was an "extract", but to respond to the request of Andre, here is the query:
SELECT Items.[TPN],
[StartStatus],
[EndStatus],
[TransactionDate],
[TransDoneBy],
[CurrentStatus],
[Title],
[Severity],
[LastChangeDate],
[ChangeDoneBy],
[Planned],
[Remaining]
FROM
(SELECT WORKITEM as 'TPN',
TFIELDCHANGE.NEWSYSTEMOPTIONID as 'StartStatus',
TFIELDCHANGE.OLDSYSTEMOPTIONID as 'EndStatus',
THISTORYTRANSACTION.LASTEDIT as 'TransactionDate',
trans_person.LASTNAME as 'TransDoneBy'
FROM dbo.THISTORYTRANSACTION JOIN dbo.TFIELDCHANGE ON (dbo.THISTORYTRANSACTION.OBJECTID = dbo.TFIELDCHANGE.HISTORYTRANSACTION)
JOIN dbo.TPERSON trans_person ON (dbo.THISTORYTRANSACTION.CHANGEDBY = trans_person.PKEY)
WHERE dbo.TFIELDCHANGE.FIELDKEY = 4 -- Only transactions regarding status
) Transactions
JOIN
(SELECT WORKITEMKEY as 'TPN',
TSTATE.LABEL as 'CurrentStatus',
PACKAGESYNOPSYS as 'Title',
TSEVERITY.LABEL as 'Severity',
TWORKITEM.LASTEDIT as 'LastChangeDate',
item_person.LASTNAME as 'ChangeDoneBy',
max(CASE TATTRIBUTEVALUE.FIELDKEY WHEN 60 THEN TATTRIBUTEVALUE.INTEGERVALUE END) as 'Planned',
max(CASE TATTRIBUTEVALUE.FIELDKEY WHEN 72 THEN TATTRIBUTEVALUE.INTEGERVALUE END) as 'Remaining'
FROM dbo.TWORKITEM JOIN dbo.TSTATE ON (dbo.TWORKITEM.STATE = dbo.TSTATE.PKEY)
JOIN dbo.TSEVERITY ON (dbo.TWORKITEM.SEVERITYKEY = dbo.TSEVERITY.PKEY)
JOIN dbo.TPERSON item_person ON (dbo.TWORKITEM.CHANGEDBY = item_person.PKEY)
JOIN dbo.TATTRIBUTEVALUE ON (dbo.TWORKITEM.WORKITEMKEY = dbo.TATTRIBUTEVALUE.WORKITEM)
WHERE dbo.TWORKITEM.STATE = 2 OR -- Current state: analyzed
dbo.TWORKITEM.STATE = 3 OR -- Current state: assigned
dbo.TWORKITEM.STATE = 4 OR -- Current state: suspended
dbo.TWORKITEM.STATE = 6 OR -- Current state: implemented
dbo.TWORKITEM.STATE = 7 OR -- Current state: verified
dbo.TWORKITEM.STATE = 13 -- Current state: verifying
GROUP BY WORKITEMKEY,
TSTATE.LABEL,
PACKAGESYNOPSYS,
TSEVERITY.LABEL,
TWORKITEM.LASTEDIT,
item_person.LASTNAME
) Items
ON Items.TPN = Transactions.TPN
ORDER BY Items.[TPN] ASC, [TransactionDate] DESC
It seems you are looking for a general way to compare the latest transaction of a group with its predecessor.
This can be done using the ROW_NUMBER() function and a self join like this:
SELECT foo
FROM (SELECT foo,
Row_number() OVER (PARTITION BY TRN ORDER BY TDate) AS RN
FROM TranTable) AS Latest
LEFT JOIN (SELECT foo,
Row_number() OVER (PARTITION BY TRN ORDER BY TDate) AS RN
FROM TranTable) AS Previous
ON Latest.RN = Previous.RN - 1
WHERE Latest.RN = 1 /* Get only the latest */
OR (/* your criteria for two latest */ AND Latest.RN IN (1,2))