我对表中的记录进行了审核。有多列,每个记录状态,用于更改1列或更多列
我需要返回一个审核结果,其中返回模式为:column
( id , alias 或 name ),之前的值,新价值等
问题在于每个新记录可能有多个列具有已更改的数据。与此同时,可审计列的数量为5,因此可以对其进行“硬编码”并更改验证。
那么是否可以以缩短的方式组合这样的查询,而不仅仅是使用UNIONS并为每列进行SELECT查询并检查更改?
假设有一个包含列的表:
id, datetime value, int value, varchar value.
如果我有2条记录,这样的数据更改如下:
id1, value1, value1, value1
id1, value2, value1, value2
然后我期待这样的审核结果:
id1, value1 as oldvalue, value2 as newvalue, column2name as columnname
id1, value1 as oldvalue, value2 as newvalue, column4name as columnname
答案 0 :(得分:2)
如果我没有错过任何内容:
WITH ranked AS (
SELECT
ChangeDate,
ColPK,
Col1,
Col2,
Col3,
Col4,
Col5,
OverallRank = ROW_NUMBER() OVER (PARTITION BY ColPK ORDER BY ChangeDate),
Col1Rank = ROW_NUMBER() OVER (PARTITION BY ColPK, Col1 ORDER BY ChangeDate),
Col2Rank = ROW_NUMBER() OVER (PARTITION BY ColPK, Col2 ORDER BY ChangeDate),
Col3Rank = ROW_NUMBER() OVER (PARTITION BY ColPK, Col3 ORDER BY ChangeDate),
Col4Rank = ROW_NUMBER() OVER (PARTITION BY ColPK, Col4 ORDER BY ChangeDate),
Col5Rank = ROW_NUMBER() OVER (PARTITION BY ColPK, Col5 ORDER BY ChangeDate)
FROM AuditTable
)
, ranked2 AS (
SELECT
ChangeDate,
ColPK,
Col1,
Col2,
Col3,
Col4,
Col5,
Col1Group = RANK() OVER (PARTITION BY ColPK, Col1 ORDER BY OverallRank - Col1Rank),
Col2Group = RANK() OVER (PARTITION BY ColPK, Col2 ORDER BY OverallRank - Col2Rank),
Col3Group = RANK() OVER (PARTITION BY ColPK, Col3 ORDER BY OverallRank - Col3Rank),
Col4Group = RANK() OVER (PARTITION BY ColPK, Col4 ORDER BY OverallRank - Col4Rank),
Col5Group = RANK() OVER (PARTITION BY ColPK, Col5 ORDER BY OverallRank - Col5Rank),
Col1Rank = ROW_NUMBER() OVER (PARTITION BY ColPK, Col1, OverallRank - Col1Rank ORDER BY ChangeDate),
Col2Rank = ROW_NUMBER() OVER (PARTITION BY ColPK, Col2, OverallRank - Col2Rank ORDER BY ChangeDate),
Col3Rank = ROW_NUMBER() OVER (PARTITION BY ColPK, Col3, OverallRank - Col3Rank ORDER BY ChangeDate),
Col4Rank = ROW_NUMBER() OVER (PARTITION BY ColPK, Col4, OverallRank - Col4Rank ORDER BY ChangeDate),
Col5Rank = ROW_NUMBER() OVER (PARTITION BY ColPK, Col5, OverallRank - Col5Rank ORDER BY ChangeDate)
FROM ranked
),
unpivoted AS (
SELECT
r.ChangeTime,
r.ColPK,
x.ColName,
ColRank = CASE x.Colname
WHEN 'Col1' THEN Col1Group
WHEN 'Col2' THEN Col2Group
WHEN 'Col3' THEN Col3Group
WHEN 'Col4' THEN Col4Group
WHEN 'Col5' THEN Col5Group
END,
Value = CASE x.Colname
WHEN 'Col1' THEN CONVERT(nvarchar(100), r.Col1)
WHEN 'Col2' THEN CONVERT(nvarchar(100), r.Col2)
WHEN 'Col3' THEN CONVERT(nvarchar(100), r.Col3)
WHEN 'Col4' THEN CONVERT(nvarchar(100), r.Col4)
WHEN 'Col5' THEN CONVERT(nvarchar(100), r.Col5)
END
FROM ranked2 r
INNER JOIN (VALUES ('Col1'), ('Col2'), ('Col3'), ('Col4'), ('Col5')) x (ColName)
ON x.ColName = 'Col1' AND Col1Rank = 1
OR x.ColName = 'Col2' AND Col2Rank = 1
OR x.ColName = 'Col3' AND Col3Rank = 1
OR x.ColName = 'Col4' AND Col4Rank = 1
OR x.ColName = 'Col5' AND Col5Rank = 1
)
SELECT
new.ChangeTime,
new.ColPK,
new.ColName,
old.Value AS OldValue,
new.Value AS NewValue
FROM unpivoted new
LEFT JOIN unpivoted old
ON new.ColPK = old.ColPK
AND new.ColName = old.ColName
AND new.ColRank = old.ColRank + 1
基本上,我们的想法是对相同值的连续组进行排名,并选择每个值的第一次出现。对于其值正在被审计的每个列都要执行此操作,并且在此过程中列不显示。之后,不相交的行集合自身,即对于每个PK和列名称,每一行都与其前一个行匹配(基于排名),以获得最终结果集的同一行中的旧值。
答案 1 :(得分:1)
这是一个更简单的查询,可以产生相同的预期结果,并且更容易修改以容纳不同数量的列或更改列名称,因为唯一的区别是PK列+每个非单行CROSS APPLY
中的-PK列。我必须添加一个ChangeDate
列 - 没有它,就无法知道插入审计表的行的顺序。
WITH ColValues AS (
SELECT
Grp = Row_Number() OVER (
PARTITION BY H.OrderID, U.ColName ORDER BY H.ChangeDate ASC, X.Which
) / 2,
H.OrderID,
H.ChangeDate,
U.*,
X.Which
FROM
dbo.OrderHistory H
CROSS APPLY (VALUES
('DeliveryDate', Convert(varchar(1000), DeliveryDate, 121)),
('Quantity', Convert(varchar(1000), Quantity)),
('SpecialNotes', Convert(varchar(1000), SpecialNotes))
) U (ColName, Value)
CROSS JOIN (VALUES (1), (2)) X (Which)
)
SELECT
V.OrderID,
V.ColName,
DateChanged = Max(V.ChangeDate),
OldValue = Max(F.Value),
NewValue = Max(T.Value)
FROM
ColValues V
OUTER APPLY (SELECT V.ColName, V.Value WHERE V.Which = 2) F
OUTER APPLY (SELECT V.ColName, V.Value WHERE V.Which = 1) T
GROUP BY
V.OrderID,
V.ColName,
V.Grp
HAVING
Count(*) = 2
AND EXISTS (
SELECT Max(F.Value)
EXCEPT SELECT Max(T.Value)
)
;
See a live demo of this query at SQL Fiddle
在SQL 2012中,使用LEAD
或LAG
分析函数可以更好地解决这个问题。我的查询中的CROSS JOIN
和Row_Number
通过复制每一行并将这些重复的行成对分配到它们自己的组(其中每个组有两行代表相邻的审计历史记录行)来模拟这一点。然后通过战略性地使用聚合,我们可以处理分组对以选择和比较它们的值。
另外,我最初使用UNPIVOT
编写了查询,但是唉,它不会保留NULL - 在我看来,这是微软的严重疏忽。如果需要,开发人员可以很容易地添加删除NULL的条件,但是当希望保留NULL时,根本不能使用它UNPIVOT
的方式。具有讽刺意味的是,使用CROSS APPLY
到UNPIVOT时,生成的代码更加紧凑,缩短了2行 - 现在转换和取消转换只需一步而不是2步。
我的示例数据是:
ChangeDate OrderID DeliveryDate Quantity SpecialNotes
----------------------- ------- ----------------------- -------- ----------------------------------------------------
2013-03-01 11:28:00.000 1 2013-04-01 00:00:00.000 25 NULL
2013-03-01 11:56:00.000 1 2013-04-01 00:00:00.000 30 NULL
2013-03-05 10:18:00.000 1 2013-04-02 00:00:00.000 30 Customer called to ask for delivery date adjustment.
2013-03-01 11:37:00.000 2 2013-03-05 00:00:00.000 17 NULL
结果行集:
OrderID ColName DateChanged OldValue NewValue
------- ------------ ----------------------- ----------------------- ---------------------------------------------------
1 DeliveryDate 2013-03-05 10:18:00.000 2013-04-01 00:00:00.000 2013-04-02 00:00:00.000
1 Quantity 2013-03-01 11:56:00.000 25 30
1 SpecialNotes 2013-03-05 10:18:00.000 NULL Customer called to ask for delivery date adjustment.
注意:由于我的查询只有一个排名函数而且没有JOIN
,因此即使在非常大的表中也会表现得非常好 - 或许比使用{{1没有支持索引的地方。审计表最好在JOIN
上有一个聚簇索引。