【问题标题】:SQL Server Foreign key with triggers used as history logger带有用作历史记录器的触发器的 SQL Server 外键
【发布时间】:2014-08-10 22:47:11
【问题描述】:

这是我的情况。我正在使用 ASP.NET MVC 5。

我的要求是将记录的添加/更新/删除操作跟踪到历史表中。

此示例有 2 个表 A 和 B 用于原始记录,另一个表 AHistories 和 BHistories 用于保存历史记录。存在一对多关系,一个 A 和多个 B。

我使用触发器来实现这一点。我被与外键相关的事情困住了。当我在触发器中将记录插入历史记录表时,我想使用指向历史记录表中的 ID 而不是原始表的外键保存记录。

这就是我在触发器中所做的。

CREATE TRIGGER A_History_Trigger ON A FOR INSERT, UPDATE, DELETE AS

SET NOCOUNT ON;

DECLARE @AHistoryID INT

IF EXISTS (SELECT * FROM Inserted)
BEGIN
    DECLARE @HistoryType NVARCHAR(6)

    IF EXISTS (SELECT * FROM Deleted)
        SET @HistoryType = 'update'
    ELSE
        SET @HistoryType = 'add'

    /* Save record from table A into AHistories table */
    INSERT INTO AHistories (HistoryID, Area, ModifiedDate, HistoryType)
    SELECT ID, Area, CURRENT_TIMESTAMP, @HistoryType
    FROM Inserted

    /* Save updated record from table B into BHistories table */
    IF @HistoryType = 'update'
        INSERT INTO BHistories (HistoryID, AHistoryID, Name, ModifiedDate, HistoryType)
        SELECT ID, SCOPE_IDENTITY(), Name, CURRENT_TIMESTAMP, @HistoryType
        FROM B
        WHERE AID = (SELECT ID FROM Inserted)
END
ELSE /* Delete */
BEGIN
    INSERT INTO AHistories (HistoryID, Area, ModifiedDate, HistoryType)
    SELECT ID, Area, CURRENT_TIMESTAMP, 'delete' FROM Deleted

    DECLARE @AID INT
    SET @AID = (SELECT ID FROM Deleted)
    SET @AID = SCOPE_IDENTITY()

    /* B */
    INSERT INTO BHistories (HistoryID, AHistoryID, Name, ModifiedDate, HistoryType)
    SELECT ID, @AHistoryID, Name, CURRENT_TIMESTAMP, 'delete'
    FROM B
    WHERE AID = @AID

    DELETE FROM B WHERE AID = @AID
END

在表 B 的触发器中,

CREATE TRIGGER B_History_Trigger ON B FOR INSERT AS

SET NOCOUNT ON;

DECLARE @AHistoryID INT

/* Is this safe? */
SET @AHistoryID = (SELECT TOP 1 ID FROM AHistories WHERE HistoryID = (SELECT TOP 1 AID FROM Inserted) ORDER BY ID DESC)

INSERT INTO BHistories (HistoryID, Name, AHistoryID, ModifiedDate, HistoryType)
SELECT ID, Name, @AHistoryID, CURRENT_TIMESTAMP, 'add' FROM Inserted

在 B 的触发器中,通过在那里执行 SELECT 获得正确的 AHistoryID 是否安全?在生产中,我担心是否存在多个用户同时执行操作时触发器会选择错误的 AHistoryID 的情况。这是可接受的方法吗?

感谢任何cmets。

【问题讨论】:

  • 如果你可以让多个人访问一个表,假设它会。无论如何,你的 id 应该仍然指向“原始”的——如果你改变它们,它会破坏历史(你怎么知道它来自哪个 id?)。我什至可能没有这些表的唯一 ID - 通常您不关心更新/删除特定行,您关心整体历史记录。因此,您正在运行SELECT 语句,这些语句要么获取所有行(带有它们的时间戳,这可能是唯一的......),要么是更复杂的东西,其中 id 无论如何都无关紧要(例如,最近的更改)。
  • 在我的历史表中,我有唯一的 ID 和 HistoryID 字段,其中 HistoryID 是原始记录的 ID。当我更新 A 时,我想知道 A 记录在该更新时有哪些 B 记录。这样,我总能看到它指向的 Bs。我错过了一点吗?
  • 您通常会使用审计时间戳重新构建它。您还应该能够使用它来获取审核唯一 ID。如果您像这样存储派生的 fks,您希望将它们存储在 addition 到原始 fks 中。您的跳汰机也不适合多排使用...

标签: sql sql-server database triggers


【解决方案1】:

问题

我在您的触发器中看到的唯一问题是您假设INSERTEDDELETED 表只有一行。

这个假设会导致这一行的第一个触发器出错:

WHERE AID = (SELECT ID FROM Inserted)

如果使用一个INSERT 语句插入多于一行。

在第二个触发器中,您通过选择 TOP 1 解决了这个问题,但这会给您带来一些错误,因为它会为插入的行之一选择 AHistories.ID,并将所有其他行保存到 BHistories .

解决方案

您可以随时加入INSERTEDDELETED 表以获得您需要的任何东西。以下是更改:

INSERT INTO BHistories (HistoryID, AHistoryID, Name, ModifiedDate, HistoryType)
SELECT B.ID, SCOPE_IDENTITY(), B.Name, CURRENT_TIMESTAMP, @HistoryType
FROM B
INNER JOIN Inserted i ON i.ID = B.AID

还有:

INSERT INTO BHistories (HistoryID, AHistoryID, Name, ModifiedDate, HistoryType)
SELECT B.ID, @AHistoryID, B.Name, CURRENT_TIMESTAMP, 'delete'
FROM B
INNER JOIN Deleted d ON d.ID = B.AID

最后一个。远程这条线:

SET @AHistoryID = (SELECT TOP 1 ID FROM AHistories WHERE HistoryID = (SELECT TOP 1 AID 

并像这样更新查询:

INSERT INTO BHistories (HistoryID, Name, AHistoryID, ModifiedDate, HistoryType)
SELECT i.ID, i.Name, ah.ID, CURRENT_TIMESTAMP, 'add' 
FROM Inserted i
INNER JOIN AHistories ah ON ah.HistoryID = i.AID

【讨论】:

  • 谢谢,看起来很不错。但是,我只是放弃了使用触发器的方法,因为对于我的技能水平和时间来说,让它与所有其他表的关系一起工作很复杂。我通过在添加/更新/删除操作后立即将记录添加到历史表来改变我在控制器中跟踪历史记录的方法。我还删除了由外键引用的记录,而不是 ON DELETE CASCADE。我能谈谈你的想法吗?
  • 我完全同意。触发器总是会增加业务的复杂性,而复杂的触发器会使情况变得更糟。关于级联删除,最好用数据层代码代替,除非在某些结构更简单或数据库由多个应用程序操作的情况下。
  • 感谢您的意见。我对这种方法的担忧是因为我正在执行代码来删除级联记录,如果用户在代码完成删除之前失去与网站的连接怎么办。这将不是一个公共站点,它将被用作管理系统,所以我预计流量会更少。该数据库仅用于此应用程序。你说“除了......结构更简单”是因为让数据库这样做以避免代码失败更安全?
  • 当然,数据库做这些任务更安全,但是随着规模的扩大,会带来更多的复杂性,并且变得更难维护。关于你关于不完整操作的音乐会:你总是可以从代码内部BEGIN一个事务,如果在事务提交之前发生了意外,数据库引擎将ROLLBACK不完整的事务。
  • 谢谢!实施 Begintransaction 和 Rollback 很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-04
  • 1970-01-01
相关资源
最近更新 更多