2012-05-28 50 views
0

我对表中的记录进行了审计。有多个列,每个记录状态用于更改1列或更多列。
我需要返回的审计结果,其中复原模式将是:columnID别名),以前的价值,新价值等
的问题是在可以有多个每个新记录都有更改数据的列。与此同时,可审计的列的数量是5,因此可以对名称进行“硬编码”并更改验证。SQL:返回每个列的记录审计

所以是有可能在不使用只是工会缩短的方式来编写这样的查询并选择查询每一列和检查的变化?

比方说,有一个与列的表:

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

您是不是还要显示更改时间(或至少某些指示更改顺序的顺序值)? –

+0

另一个问题可能是审计列的类型。如果它们不同,则可能需要将它们全部转换为字符串,以便能够在相同的列中输出它们(即'previous value'和'new value')。 –

+0

是的,我将不得不返回发生审计的日期,是的,我将不得不将数据转换为varchar。 – drunkcamel

回答

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和列名称,每行都与其前任匹配(基于排名),以获得最终结果集的同一行中的旧值。

0

这里是产生相同的期望结果的简单查询,并且是很容易修改,以适应不同的列数或改变列名,因为唯一的区别是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年,这将是解决了一个LEADLAG分析功能更好。我的查询中的CROSS JOINRow_Number通过复制每一行并将这些重复行成对分配到它们自己的组(其中每个组具有表示相邻审计历史记录行的两行)来模拟此操作。然后通过策略使用聚合,我们可以处理分组对以选择和比较它们的值。

另外,我原来写的查询与UNPIVOT,但很可惜,它不保留空值 - 严重疏忽微软,在我看来。如果需要,开发人员可以很容易地添加一个删除NULL的条件,但是在希望保留NULL时根本无法使用UNPIVOT。具有讽刺意味的是,由此产生的代码更加紧凑,缩短了2行,使用CROSS APPLY来处理UNPIVOT - 现在转换和未转换发生在一步而不是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 S,这甚至会在效果极为显着非常大的表格 - 比使用没有支持索引的JOIN的解决方案更好一些。审计表最好在PK, ChangeDate上有一个聚集索引。