2013-10-19 63 views
5

对于测井系统的我想有以下数据库模式:INSTEAD-OF-INSERT触发器的多个表

CREATE TABLE [dbo].[Categories] (
    [Id]  INT   IDENTITY (1, 1) NOT NULL, 
    [App]  NVARCHAR (30) NULL, 
    [Source] NVARCHAR (30) NULL, 
    [LogLevel] NVARCHAR (5) NULL, 
    [Logger] NVARCHAR (120) NULL, 
    CONSTRAINT [PK_Categories] PRIMARY KEY NONCLUSTERED ([Id] ASC), 
    CONSTRAINT [UK_Categories] UNIQUE NONCLUSTERED ([App] ASC, [Source] ASC, [LogLevel] ASC, [Logger] ASC) 
); 

CREATE TABLE [dbo].[Occurences] (
    [PointInTime] BIGINT NOT NULL, 
    [CategoryId] INT NOT NULL, 
    [Noise]  INT NOT NULL, 
    CONSTRAINT [PK_Occurences] PRIMARY KEY CLUSTERED ([PointInTime] ASC, [CategoryId] ASC, [Noise] ASC), 
    CONSTRAINT [FK_Category] FOREIGN KEY ([CategoryId]) REFERENCES [Categories] ([Id]) 
); 

的设计目标是允许大量日志数据作为更昂贵的琴弦在单独的表格中被分解。

在语义上,这两个表构成由该视图中定义的单个逻辑表:

CREATE VIEW [dbo].[HistoricLogEntries] 
    AS SELECT o.PointInTime, o.Noise, c.App, c.[Source], c.LogLevel, c.Logger 
    FROM Occurences o 
    JOIN Categories c ON o.CategoryId = c.Id; 

我现在想限定在视图上的,而不是-的嵌件触发,这是在我的烦恼开始。我有以下尝试:

CREATE TRIGGER InsteadTrigger on [dbo].[HistoricLogEntries] 
INSTEAD OF INSERT 
AS 
BEGIN 
    INSERT INTO Categories 
    SELECT i.App, i.[Source], i.LogLevel, i.Logger 
    FROM INSERTED i; 

    INSERT INTO Occurences 
    SELECT i.PointInTime, i.Noise, c.Id AS CategoryId 
    FROM INSERTED i 
    JOIN Categories c ON i.App = c.App AND i.[Source] = c.[Source] AND i.LogLevel = c.LogLevel AND i.Logger = c.Logger; 
END 

有一个明显的第一个问题,第一个插入不检查元组是否已经在数据库中。我知道如何在单个值插入的情况下做到这一点,但在这里我必须考虑多行插入。

另一件我不能解释的事情是,如果第一次插入成功,触发器甚至不起作用。我得到一个外键违例 - 就好像第一个插入没有实际插入任何东西。

我认为这应该是一个常见的设置,所以也许有人有一些类似的示例代码?

回答

8

对于SQL2008 +我会用MERGE语句插入新的类别:

MERGE dbo.Categories WITH (HOLDLOCK) AS c 
USING INSERTED AS i ON i.App = c.App 
AND i.[Source] = c.[Source] 
AND i.LogLevel = c.LogLevel 
AND i.Logger = c.Logger 
WHEN NOT MATCHED BY TARGET THEN 
    INSERT (App, [Source], LogLevel, Logger) 
    VALUES (i.App, i.[Source], i.LogLevel, i.Logger); 

我以前HOLDLOCK表提示to prevent race condition

[...]为了防止并发会话从与插入数据相同的 键,必须获取不兼容的锁以确保只有一个会话 可以读取密钥(本例中为我的注释:UK_Categories唯一索引),并且必须保持该锁定,直到事务 完成[...]

并防止FK错误:

The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Category". The conflict occurred in database "Test", table "dbo.Categories", column 'Id'. 

我会内第二INSERT从而增加每一列的名称:

INSERT INTO Occurences (PointInTime, Noise, CategoryId) 
SELECT i.PointInTime, i.Noise, c.Id AS CategoryId 
FROM INSERTED i 
JOIN Categories c ON i.App = c.App 

原因这些FK错误是列顺序内的不一致:

1)CREATE中的列顺序表是

[PointInTime] BIGINT NOT NULL, 
    [CategoryId] INT NOT NULL, 
    [Noise]  INT NOT NULL, 

2)在第二插入列的顺序是不同的(见的CategoryId VS噪音):

INSERT INTO Occurences 
-- or INSERT INTO Occurences (PointInTime, CategoryId, Noise) 
SELECT i.PointInTime, i.Noise, c.Id AS CategoryId 
FROM ... 

这是我的解决方案:

ALTER TRIGGER InsteadTrigger on [dbo].[HistoricLogEntries] 
INSTEAD OF INSERT 
AS 
BEGIN  
    MERGE dbo.Categories WITH (HOLDLOCK) AS c 
    USING INSERTED AS i ON i.App = c.App 
    WHEN NOT MATCHED BY TARGET THEN 
     INSERT (App) 
     VALUES (i.App); 

    INSERT INTO Occurences (PointInTime, Noise, CategoryId) 
    SELECT i.PointInTime, i.Noise, c.Id AS CategoryId 
    FROM INSERTED i 
    JOIN Categories c ON i.App = c.App 
END 
GO 
+1

这真的很全面,在我发布这个问题后很快。非常感谢你! – John