これがSQLServerソリューションです。
アップデート
アイデアは、Olafによって提案された多数のNESTED LOOP結合を回避することです。これは、それらがおおよそO(N * M)の複雑さを持ち、パフォーマンスに非常に悪いためです。MERGED JOINSの複雑さはO(N Log(N)+ M Log(M))であり、実際のシナリオにはるかに適しています。
以下のクエリは次のように機能します。
RankedCTEアカウントで分割され、時間を表すidでソートされた各IDに行番号を割り当てるサブクエリです。したがって、この出力の下のデータについては
SELECT
id,
account_num,
status_code,
ROW_NUMBER() OVER (PARTITION BY account_num ORDER BY id DESC) AS item_rank
FROM dbo.Test
だろう:
id account_num status_code item_rank
----------- ----------- ----------- ----------
87 1 Z 1
82 1 Y 2
64 1 X 3
103 2 Z 1
91 2 X 2
72 2 Y 3
それらに番号を付けたら、次のように結果を結合します。
WITH RankedCTE AS
(
SELECT
id,
account_num,
status_code,
ROW_NUMBER() OVER (PARTITION BY account_num ORDER BY id DESC) AS item_rank
FROM dbo.Test
)
SELECT
*
FROM
RankedCTE A
INNER JOIN RankedCTE B ON
A.account_num = B.account_num
AND A.item_rank = B.item_rank - 1
これにより、同じテーブル内のイベントと先行イベントが提供されます
id account_num status_code item_rank id account_num status_code item_rank
----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------
87 1 Z 1 82 1 Y 2
82 1 Y 2 64 1 X 3
103 2 Z 1 91 2 X 2
91 2 X 2 72 2 Y 3
最後に、コード「X」の前のイベントと「X」ではないコードのイベントを取得する必要があります。
WITH RankedCTE AS
(
SELECT
id,
account_num,
status_code,
ROW_NUMBER() OVER (PARTITION BY account_num ORDER BY id DESC) AS item_rank
FROM dbo.Test
)
SELECT
A.id,
A.account_num,
A.status_code
FROM
RankedCTE A
INNER JOIN RankedCTE B ON
A.account_num = B.account_num
AND A.item_rank = B.item_rank - 1
AND A.status_code <> 'X'
AND B.status_code = 'X'
このクエリのクエリプランと@OlafDietscheソリューション(バージョンの1つ)は以下のとおりです。

データ設定スクリプト
CREATE TABLE dbo.Test
(
id int not null PRIMARY KEY,
account_num int not null,
status_code nchar(1)
)
GO
INSERT dbo.Test (id, account_num, status_code)
SELECT 64 , 1, 'X' UNION ALL
SELECT 82 , 1, 'Y' UNION ALL
SELECT 72 , 2, 'Y' UNION ALL
SELECT 87 , 1, 'Z' UNION ALL
SELECT 91 , 2, 'X' UNION ALL
SELECT 103, 2, 'Z'