【问题标题】:Capturing SQL row changes (multiple columns) using existing flag column使用现有标志列捕获 SQL 行更改(多列)
【发布时间】:2018-08-07 05:54:48
【问题描述】:

我有一张车辆登记表,如下所示:

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 值作为同一行中的不同列。

所以上面的查询会返回如下内容:

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 Server 2005

SQLFiddlehttp://sqlfiddle.com/#!18/f579f/1

【问题讨论】:

    标签: sql sql-server sql-server-2005


    【解决方案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。

    【讨论】:

    • 哇,谢谢。当状态从一个更改到另一个更改时,您将如何考虑只想要最近的更改? (例如,只看到从但丁到苏珊的变化,而不是摩天到但丁)
    • @JayM 。 . .公平的问题。这似乎是您在原始问题中想要的。我最初错过了那部分。
    • 漂亮,感谢您的帮助。另外,谢谢你让我看到Apply。这改变了游戏规则
    【解决方案2】:
    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
    
    车辆识别号 |旧名称 |新名称 |日期 --: | :------- | :-------- | :----------------- 1 |弗雷德 |汤姆 | 2017 年 8 月 6 日 00:00:00 2 |南希 |吉姆 | 2018 年 5 月 2 日 00:00:00 3 |克拉伦斯 |达琳 | 2018 年 11 月 2 日 00:00:00 5 |但丁 |苏珊 | 13/02/2018 00:00:00

    dbfiddle here

    【讨论】:

      猜你喜欢
      • 2017-05-13
      • 1970-01-01
      • 1970-01-01
      • 2019-07-19
      • 1970-01-01
      • 2017-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多