2012-05-28 49 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上有一個聚集索引。