使用现有标志列捕获 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
但就 PIVOT
ing 而言,我不确定如何考虑每个 VIN
可能有 2 个或可能有 10 个条目的事实。我真的只想要最近的两个(最近的是 Change
)。
我可以在应用程序级别对上述查询进行额外处理,但我想知道如何在 SQL 中执行此操作。
RDBMS:MS SQL 服务器 2005
这在 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
我的车辆登记 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
但就 PIVOT
ing 而言,我不确定如何考虑每个 VIN
可能有 2 个或可能有 10 个条目的事实。我真的只想要最近的两个(最近的是 Change
)。
我可以在应用程序级别对上述查询进行额外处理,但我想知道如何在 SQL 中执行此操作。
RDBMS:MS SQL 服务器 2005
这在 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