1つの行の最新のアクティビティをログに記録するために4つの列を持つテーブルを正規化するにはどうすればよいですか。
作成日
によって作成された
変更日
変更された
数十のテーブルがあり、すべてに4つの列があります。これを効率的かつ柔軟に正規化する方法またはパターンはありますか?
「最新のアクティビティ」をログに記録するのではなく、完全な監査証跡を保持することをお勧めします。スペース要件を最小限に抑えるために、3つのテーブルが必要になる場合があります。
_CREATE TABLE dbo.Users
(
UserID TINYINT IDENTITY(1,1) PRIMARY KEY, -- assuming <= 255 users
Username NVARCHAR(128) NOT NULL UNIQUE,
/* , other columns */
);
CREATE TABLE dbo.Tables
(
TableID TINYINT IDENTITY(1,1) PRIMARY KEY, -- assuming <= 255 tables
Name NVARCHAR(128) NOT NULL UNIQUE
/* , other columns */
);
_
この監査テーブルでは、監査対象のすべてのテーブルにINT
主キー(またはINT
)。異なるデータ型や複合主キーがある場合、これは明らかに複雑になります。その場合、異なる監査テーブルを検討するだけでかまいません。1つの行の最後の変更のみを保持するよりもさらに重要です。
_CREATE TABLE dbo.AuditLog
(
TableID TINYINT NOT NULL
FOREIGN KEY REFERENCES dbo.Tables(TableID),
ID INT, -- loose reference to entity table's PK
Action CHAR(1) CHECK (Action IN ('I', 'U', 'D')),
UserID TINYINT NULL -- just in case
FOREIGN KEY REFERENCES dbo.Users(UserID),
EventDateTime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
_
(質問で言及されているものを除いて、すべてのクエリパターンがわからないため、クラスター化インデックスを意図的に指定しなかったことに注意してください。EventDateTime
、特に最近発生したイベントを照会する場合、および/または定期的にデータをパージする場合、これは決して悪い考えではありません。これにより、少なくとも新しい行が「末尾」に追加されることが保証されます。先頭列としてTableID
のようなものを選択した場合、ページ分割の代わりにテーブル全体で発生します。)
これで、トリガーはユーザー名を確認できます(認証方法は不明ですが、これはSUSER_SNAME()
を使用する場合があります)。処理するテーブルがわかっているため、アクションを決定できるため、追加する必要がありますID
/inserted
のdeleted
(s)。 FooID
という主キーを持つ_dbo.foo
_という名前のテーブルを想定するHokeyの例:
_INSERT dbo.tables(Name) SELECT N'dbo.foo';
GO
CREATE TRIGGER dbo.AuditFoo
ON dbo.foo
FOR INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @UserID TINYINT, @TableID TINYINT, @now DATETIME;
SELECT @UserID = UserID -- could be NULL
FROM dbo.Users WHERE Username = SUSER_SNAME();
SELECT @TableID = TableID
FROM dbo.Tables WHERE Name = N'dbo.foo';
-- inserts
INSERT dbo.AuditLog(TableID, ID, Action, UserID)
SELECT @TableID, FooID, 'I', @UserID
FROM inserted AS i WHERE NOT EXISTS
(SELECT 1 FROM deleted AS d WHERE d.FooID = i.FooID);
-- updates
INSERT dbo.AuditLog(TableID, ID, Action, UserID)
SELECT @TableID, FooID, 'U', @UserID
FROM inserted AS i WHERE EXISTS
(SELECT 1 FROM deleted AS d WHERE d.FooID = i.FooID);
-- deletes
INSERT dbo.AuditLog(TableID, ID, Action, UserID)
SELECT @TableID, FooID, 'D', @UserID
FROM deleted AS d WHERE NOT EXISTS
(SELECT 1 FROM inserted AS i WHERE i.FooID = d.FooID);
END
GO
_
ステートメント/トリガーの呼び出しごとに、これらの挿入の1つだけが起動します(まあ、まとまって言えば、トリガー自体はMERGE
のイベントで複数回起動します)。
ユーザー/テーブルのリストを保持したくない場合は、これを常に動的に入力できます(ただし、リストを厳密に制御していない場合は、上記のTINYINT
提案を真剣に再検討することができます)。例えば。初めて表示していて、手動でインベントリを作成していないユーザーをキャプチャするには:
_ SELECT @UserID = UserID FROM dbo.Users WHERE Username = SUSER_SNAME();
IF @UserID IS NULL
BEGIN
INSERT dbo.Users(Username) SELECT SUSER_SNAME();
SELECT @UserID = SCOPE_IDENTITY();
END
_
しかし、テーブルの場合、これはあまり意味がありません。トリガーを作成するときに行を追加するだけで、実行時に導出する代わりにTableID
をハードコーディングすることもできます。
_dbo.AuditLog
_でデータを収集すると、特定のタイプのlastアクションに関する情報を簡単に取得でき、テーブル、ユーザー、または個々のエンティティ。これらのクエリの作成に支援が必要な場合は、スキーマ、いくつかのサンプル行、および期待される/期待される結果を使用して、新しい質問を開始してください。 SQLFiddle を使用すると非常に役立ちます。