SQL 触发器 - 更新计数

Ale*_*lee 4 trigger sql-server sql-server-2014

所以我需要创建一个触发器,为每个案例创建一个新的证据编号。例如,案例 #1 可以有证据 # 的 1、2、3、4 等等。案例 #2 也可以有证据 # 的 1、2、3 等。

所以我有一个“Case”表(CaseID 为 PK)和一个“Evidence”表(EvidenceNum 为 PK,CaseID 为 FK 和相关属性)

因此,每次我搜索特定的 caseID 时,我都希望填充一个新的“EvidenceID”列。例如证据项目#1、#2 等。所以这些数字可以在每种情况下重复。因此,为什么这不是主键。EvidenceNum 是主键,但最终用户不会看到它。有什么帮助吗??

Han*_*non 6

我会使用存储过程而不是触发器来实现这一点。使用单独的密钥表来存储每个案例的最后使用的证据编号。

我模拟了一个最小可行的完整示例。

如果对象已经存在,则从 tempdb 中删除它们,以便我们可以根据需要修改代码。

USE tempdb;

IF OBJECT_ID(N'dbo.AddCase', N'P') IS NOT NULL
DROP PROCEDURE dbo.AddCase;
IF OBJECT_ID(N'dbo.AddEvidence', N'P') IS NOT NULL
DROP PROCEDURE dbo.AddEvidence;
IF OBJECT_ID(N'dbo.EvidenceKeys', N'U') IS NOT NULL
DROP TABLE dbo.EvidenceKeys;
IF OBJECT_ID(N'dbo.Evidence', N'U') IS NOT NULL
DROP TABLE dbo.Evidence;
IF OBJECT_ID(N'dbo.Cases', N'U') IS NOT NULL
DROP TABLE dbo.Cases;
GO
Run Code Online (Sandbox Code Playgroud)

创建一个 Cases and Evidence 表,以及一个 EvidenceKey 表来存储递增的证据编号。

CREATE TABLE dbo.Cases
(
    CaseID int NOT NULL IDENTITY(1,1)
        CONSTRAINT PK_Cases
        PRIMARY KEY CLUSTERED
) ON [PRIMARY];

CREATE TABLE dbo.Evidence
(
    EvidenceID int NOT NULL IDENTITY(1,1)
        CONSTRAINT PK_Evidence
        PRIMARY KEY CLUSTERED
    , CaseID int NOT NULL
        CONSTRAINT FK_Evidence_CaseID
        FOREIGN KEY 
        REFERENCES dbo.Cases(CaseID)
    , EvidenceNum int NOT NULL
    , CONSTRAINT UQ_EvidenceNum
        UNIQUE (CaseID, EvidenceNum)
);

CREATE TABLE dbo.EvidenceKeys
(
    CaseID int NOT NULL
        CONSTRAINT PK_EvidenceKeys
        PRIMARY KEY CLUSTERED
    , MaxEvidenceNum int NOT NULL
);
GO
Run Code Online (Sandbox Code Playgroud)

创建用于添加新案例的过程。您需要为此添加参数,例如案例名称、日期等。

CREATE PROCEDURE dbo.AddCase
(
    @CaseID int OUTPUT
)
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @Cases TABLE
    (
        CaseID int NOT NULL
    );
    INSERT INTO dbo.Cases 
    OUTPUT inserted.CaseID 
    INTO @Cases (CaseID)
    DEFAULT VALUES;
    SELECT @CaseID = CaseID
    FROM @Cases;
END
GO
Run Code Online (Sandbox Code Playgroud)

创建一个程序来添加证据。同样,这只是一个概念验证,因此您需要添加参数来处理实际的证据项目详细信息。

CREATE PROCEDURE dbo.AddEvidence
(
    @CaseID int
    , @EvidenceID int OUTPUT
)
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @MaxEvidences TABLE
    (
        MaxEvidenceNum int NOT NULL
    );
    SET @EvidenceID = NULL;
    UPDATE dbo.EvidenceKeys
    SET MaxEvidenceNum += 1
    OUTPUT inserted.MaxEvidenceNum
    INTO @MaxEvidences(MaxEvidenceNum)
    WHERE dbo.EvidenceKeys.CaseID = @CaseID;
    SELECT @EvidenceID = MaxEvidenceNum
    FROM @MaxEvidences;
    IF @EvidenceID IS NULL
    BEGIN
        INSERT INTO dbo.EvidenceKeys (CaseID, MaxEvidenceNum)
        VALUES (@CaseID, 1);
        SET @EvidenceID = 1;
    END
    INSERT INTO dbo.Evidence (CaseID, EvidenceNum)
    VALUES (@CaseID, @EvidenceID);
END;
GO
Run Code Online (Sandbox Code Playgroud)

插入一些示例数据:

DECLARE @CaseID int;
DECLARE @EvidenceID int;

EXEC dbo.AddCase @CaseID OUT;
EXEC dbo.AddEvidence @CaseID, @EvidenceID OUT;
SELECT @EvidenceID;
EXEC dbo.AddEvidence @CaseID, @EvidenceID OUT;
SELECT @EvidenceID;
EXEC dbo.AddEvidence @CaseID, @EvidenceID OUT;
SELECT @EvidenceID;

EXEC dbo.AddCase @CaseID OUT;
EXEC dbo.AddEvidence @CaseID, @EvidenceID OUT;
SELECT @EvidenceID;
EXEC dbo.AddEvidence @CaseID, @EvidenceID OUT;
SELECT @EvidenceID;
EXEC dbo.AddEvidence @CaseID, @EvidenceID OUT;
SELECT @EvidenceID;
Run Code Online (Sandbox Code Playgroud)

每次执行dbo.AddEvidence都会在单个原子操作dbo.EvidenceKeys中为给定的表增加值@CaseID,从而减少锁定成为问题的机会。

SELECT *
FROM dbo.Cases c
    INNER JOIN dbo.Evidence e ON c.CaseID = e.CaseID
Run Code Online (Sandbox Code Playgroud)

select以上结果:

???????????????????????????????????????????????????
? 案例ID ?证据 ID ? 案例ID ?证据编号 ?
???????????????????????????????????????????????????
? 1 ? 1 ? 1 ? 1 ?
? 1 ? 2 ? 1 ? 2 ?
? 1 ? 3 ? 1 ? 3 ?
? 2 ? 4 ? 2 ? 1 ?
? 2 ? 5 ? 2 ? 2 ?
? 2 ? 6 ? 2 ? 3 ?
???????????????????????????????????????????????????

由于获取EvidenceKey任何给定的最大值CaseID并更新dbo.EvidenceKeys表发生在单个原子语句中,因此死锁的机会大大减少,无需锁定提示。

为了测试这个设计,我运行了以下代码。第一部分创建了 100 个“案例”,每个案例有 3 行“证据”。然后,在 3 个单独的会话中,第二段代码将 100,000 行插入Evidence表中,将每个证据行随机分配给随机选择的案例。没有发生死锁,这个过程在我旧的、缓慢的开发工作站上花费了不到 1 分钟的时间。

DECLARE @loop int = 0;
DECLARE @CaseID int;
DECLARE @EvidenceID int;
WHILE @loop < 100
BEGIN
    EXEC dbo.AddCase @CaseID OUT;
    EXEC dbo.AddEvidence @CaseID, @EvidenceID OUT;
    EXEC dbo.AddEvidence @CaseID, @EvidenceID OUT;
    EXEC dbo.AddEvidence @CaseID, @EvidenceID OUT;
    SET @loop += 1;
END
GO
Run Code Online (Sandbox Code Playgroud)

这件作品应该在 3 个(或更多)单独的会话中运行:

DECLARE @loop int = 0;
DECLARE @CaseID int;
DECLARE @EvidenceID int;
WHILE @loop < 100000
BEGIN
    SET @CaseID = (SELECT TOP (1) CaseID FROM dbo.Cases ORDER BY CRYPT_GEN_RANDOM(10));
    EXEC dbo.AddEvidence @CaseID, @EvidenceID OUT;
    SET @loop += 1;
END
Run Code Online (Sandbox Code Playgroud)