Dee*_*ons 3 normalization database-design sql-server log
如何规范化具有四列的表以记录单行的最新活动:
创建于
由...制作
修改时间
修改者
我们有几十个表,都有这 4 列。有没有办法或模式以有效和灵活的方式对此进行规范化?
我建议不要记录“最新活动”,而是保留完整的审计跟踪。为了最小化空间需求,您可能需要三个表:
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 */
);
Run Code Online (Sandbox Code Playgroud)
该审计表假设所有的审计表中有一个INT
主键(或将一个PK适合进入INT
)。如果您有不同的数据类型或复合主键,这显然会更加复杂,在这种情况下,您可能只考虑不同的审计表 - 恕我直言,这比仅保留任何一行的最后修改更有价值。
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
);
Run Code Online (Sandbox Code Playgroud)
(请注意,我故意没有规定聚集索引应该是什么,因为除了问题中提到的查询模式之外,我不知道您的所有查询模式。很可能您希望在 上进行聚类EventDateTime
,特别是如果您将查询最近发生的事件,和/或您将定期清除数据,这绝不是一个坏主意。这至少将确保将新行添加到表的“末尾”而不是页面如果您选择诸如TableID
作为前导列之类的内容,则会在整个地方发生分裂。)
现在您的触发器可以检查用户名(不确定您的身份验证方法,但这可能是通过SUSER_SNAME()
),它知道它正在处理哪个表,它可以确定操作,因此它只需添加ID
来自inserted
/的(s) deleted
。Hokey 示例,假设一个表命名dbo.foo
为一个名为的主键FooID
:
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
Run Code Online (Sandbox Code Playgroud)
每个语句/触发器调用中只会触发这些插入中的一个(好吧,为了学究,触发器本身将在 的情况下触发多次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
Run Code Online (Sandbox Code Playgroud)
但是对于桌子来说,这没有多大意义。只需在创建触发器时添加行,您甚至可以硬编码TableID
而不是在运行时派生它。
在 中收集数据后dbo.AuditLog
,您应该能够轻松获取有关任何特定类型的最后一个操作的信息,仅限于表、用户甚至单个实体。如果您在构建任何这些查询时需要帮助,请使用架构、一些示例行和所需/预期结果开始一个新问题。使用SQLFiddle会很有帮助。