使用现有标志列捕获 SQL 行更改(多列)

Capturing SQL row changes (multiple columns) using existing flag column

我的车辆登记 table 如下:

CREATE TABLE [registrations]
(
    [VIN] int,
    [Name] nvarchar(255),
    [Action] nvarchar(255),
    [Date] DateTime
);

INSERT INTO registrations VALUES 
(1, 'John', 'Add', '2017-01-01'),
(1, '', 'Remove', '2017-01-16'),
(1, 'Fred', 'Add', '2017-02-25'),
(1, 'Tom', 'Change', '2017-06-08'),
(2, 'Nancy', 'Add', '2018-01-15'),
(2, 'Jim', 'Change', '2018-02-05'),
(3, 'Clarence', 'Add', '2018-02-10'),
(3, 'Darlene', 'Change', '2018-02-11'),
(4, 'Charlotte', 'Add', '2018-02-11'),
(5, 'Ferris', 'Add', '2018-02-12'),
(5, 'Dante', 'Change', '2018-02-12'),
(5, 'Susan', 'Change', '2018-02-13');

我试图仅捕获操作 Change,但我希望更改前后的 Name 值作为同一行中的不同列。

所以上面的查询会 return 类似于:

VIN    OldName    NewName    Date
1      Fred       Tom        2017-02-25
2      Nancy      Jim        2018-02-05
3      Clarence   Darlene    2018-02-11
5      Dante      Susan      2018-02-13

注意:我排除了只有 1 笔交易的 VIN,并且 Change 可以来自多个状态(例如 Add)。

其他答案 (1) 显示了一个很好的方法来做到这一点,但是通过监视单个列的变化。我有多个列在更改(Name 只是一个示例),其中一个标志列 (Action) 表示对任何行进行了更改。

我可以使用另一个答案 (2) 的指导将其转换为类似日志的可用格式:

WITH T AS
(
    SELECT *, COUNT(*) OVER (PARTITION BY VIN) as Cnt
    FROM [registrations]
)
SELECT [VIN], [Action], [Name], [Date]
FROM T
WHERE Cnt > 1
order by [VIN], [Date] desc

但就 PIVOTing 而言,我不确定如何考虑每个 VIN 可能有 2 个或可能有 10 个条目的事实。我真的只想要最近的两个(最近的是 Change)。

我可以在应用程序级别对上述查询进行额外处理,但我想知道如何在 SQL 中执行此操作。

RDBMS:MS SQL 服务器 2005

SQL小提琴http://sqlfiddle.com/#!18/f579f/1

这在 SQL Server 2012 中使用 LAG() 更容易。在 SQL Server 2005 中,您可以使用相关子查询或 APPLY:

SELECT r.VIN, r.Name as OldName, rprev.Name as NewName, r.Date
FROM registrations r OUTER APPLY
     (SELECT TOP (1) r2.*
      FROM registrations r2
      WHERE r2.VIN = r.VIN AND r2.[Date] < r.[Date]
      ORDER BY r2.[Date] DESC
     ) rprev
WHERE r.action = 'Change'
ORDER BY r.[VIN], r.[Date] DESC;

Here 就是 SQL Fiddle.

编辑:

哦,我明白了,每个 VIN 只需要一行。这是一种方法:

SELECT r.*, rprev.Name
FROM (SELECT r.*,
             ROW_NUMBER() OVER (PARTITION BY VIN ORDER BY [Date] DESC) as seqnum
      FROM registrations r
      WHERE r.action = 'Change'
     ) r OUTER APPLY
     (SELECT TOP (1) r2.*
      FROM registrations r2
      WHERE r2.VIN = r.VIN AND r2.[Date] < r.[Date]
      ORDER BY r2.[Date] DESC
     ) rprev
WHERE seqnum = 1;

Here 是此版本的 SQL Fiddle。

with x as
(
    select VIN, Name, Action, [Date],
           row_number() over (partition by VIN order by VIN, [date]) rn,
           count(*) over (partition by VIN) cnt
    from   registrations
)
select x.VIN, 
       (select x1.Name
        from   x x1
        where  x1.VIN = x.VIN
        and    x1.rn = x.rn - 1) as OldName,
       x.Name as NewName, 
       [Date]
from   x
where  x.[Action] = 'Change'
and    x.cnt > 1
and    x.rn = (select top(1) x2.rn from x x2 where x2.VIN = x.Vin order by x2.VIN, x2.rn desc)

GO
VIN | OldName  | NewName | Date               
--: | :------- | :------ | :------------------
  1 | Fred     | Tom     | 08/06/2017 00:00:00
  2 | Nancy    | Jim     | 05/02/2018 00:00:00
  3 | Clarence | Darlene | 11/02/2018 00:00:00
  5 | Dante    | Susan   | 13/02/2018 00:00:00

dbfiddle here