0

テーブル内のレコードを監査しています。複数の列があり、各レコードには 1 つ以上の列の変更が記載されています。戻りパターンが( idエイリアスまたは名前)、以前の値、新しい値などに
なる監査結果を返す必要があります。 問題は、新しいレコードごとに変更されたデータを含む複数の列が存在する可能性があることです。同時に、監査可能な列の数は 5 であるため、そこに名前を付けて検証を変更することが可能です。column

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
4

2 に答える 2

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 および列名について、すべての行が (ランキングに基づいて) 前の行と照合され、最終結果セットの同じ行の古い値が取得されます。

于 2012-05-28T18:44:43.573 に答える
1

これは、同じ目的の結果を生成するより単純なクエリであり、PK 列 + 非 PK-の列CROSS APPLY。列を追加する必要がありました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)
   )
;

SQL Fiddle でこのクエリのライブ デモを参照してください

SQL 2012 では、これはLEADまたはLAG分析関数でより適切に解決されます。私のクエリのCROSS JOINandRow_Numberは、各行を複製し、これらの複製された行をペアで独自のグループに割り当てることで、これをシミュレートします (各グループには、隣接する監査履歴行を表す 2 つの行があります)。次に、集計を戦略的に使用することにより、グループ化されたペアを処理して、それらの値を選択および比較できます。

また、私は最初に を使用してクエリを作成しましUNPIVOTたが、悲しいかな、これは NULL を保持しません。Microsoft による重大な見落としです。開発者が必要に応じて NULL を削除する条件を追加するのは簡単でしたが、UNPIVOTNULL を保持したい場合は、この方法はまったく使用できません。皮肉なことに、結果のコードはよりコンパクトになり、CROSS APPLYUNPIVOT を使用して 2 行短くなりました。変換とアンピボットが 2 ステップではなく 1 ステップで行われるようになりました。

私のサンプルデータは次のとおりです。

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.

注: 私のクエリにはランキング関数が 1 つしかなく、 s がないため、これは非常に大きなテーブルでも非常にうまく機能します。おそらく、サポート インデックスがない場所でJOINを使用するソリューションよりも桁違いに優れています。JOIN監査テーブルにクラスター化インデックスを設定するのが最適ですPK, ChangeDate

于 2013-04-26T18:47:33.117 に答える