创建检查另一个日期的触发器 table

Create trigger that checks for a date from another table

我必须编写一个触发器来检查 MaintenanceDate 是否大于或等于 Plant(在另一个 table 中)的 BirthDate。我已经编写了一个函数,将 Plant 的生日设为 Id

我有问题如果我在维护中插入一堆项目 table,触发器就没有正确完成工作。

CREATE TRIGGER TrgCheckBirthDateOfPlant
ON Maintenance
INSTEAD OF INSERT
AS  
    DECLARE
         @PlantId AS int,
         @MaintenanceDate AS date;
BEGIN
    SELECT @PlantId = PlantId 
    FROM inserted;

    SELECT @MaintenanceDate = MaintenanceDate 
    FROM inserted;

    IF (@MaintenanceDate >= dbo.GetBirthDateById(@PlantId))
        INSERT INTO Maitenance 
            SELECT I.PlantId, I.MaintenanceDate, I.description, I.type  
            FROM inserted I;
END 

涉及的table是:

CREATE TABLE Maintenance
(
    id int IDENTITY PRIMARY KEY,
    PlantId int 
         REFERENCES Plant(id) NOT NULL,
    MaintenanceDate datetime NOT NULL,
    description varchar(250),
    type varchar(15)
);

CREATE TABLE Plant
(
    id int IDENTITY PRIMARY KEY,
    name varchar(30) NOT NULL,
    birthDate NOT NULL,
    height decimal (6, 1) CHECK (height<= 12000)
    price decimal(10, 2) CHECK (price > 0),
); 

触发器中包含一些内容。返回额外的计数通常不是一件好事。 (我不确定为 instead of 触发器返回什么计数。包含以下语句的规则可能需要调查。我们不希望原始插入和替换计数都存在。SQL 服务器是否抑制如果有 instead of 触发器,算作原始插入吗?)

SET NOCOUNT ON

没事做的测试。 @@ROWCOUNT(或 bigint 版本)对此不再可靠,因为源可以是合并语句。如果触发器在删除时触发,则还需要测试“已删除”table。下面的第一条语句足以用于插入。第二个适用于插入、更新和删除。 (不要同时使用两者。)

IF NOT EXISTS(SELECT * FROM inserted) RETURN -- no rows inserted or updated
IF NOT EXISTS(SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted) RETURN -- no rows inserted, updated, or deleted

现在插入的每条记录都需要测试。看起来每个都可能来自不同的植物。我会检查函数中的逻辑并避免使用该函数。在基于集合的查询中使用标量函数可能会成为性能杀手。大概是这样的。

INSERT INTO Maintenance 
SELECT I.PlantId, I.MaintenanceDate, I.description, I.type  
FROM inserted I
LEFT JOIN Plant p
ON p.ID = I.PlantId
WHERE I.MaintenanceDate >= p.BirthDate;

为了测试,您没有“插入”table。你可以使用真正的table。不要忘记排除插入。在良好的数据样本上对其进行测试,以确保获得所需的结果。我想现有数据有将要选择的日期。

SELECT Top 100 I.PlantId, I.MaintenanceDate, I.description, I.type  
FROM Maintenance I
LEFT JOIN Plant p
ON p.Id = I.PlantId
WHERE I.MaintenanceDate >= p.BirthDate;

比触发器更好的选择,a little-known trick 涉及索引视图,它将强制实施 multi-table 约束。

基本上是这样的:

  • 创建一个 table 正好包含两行:
CREATE TABLE dbo.TwoRows (dummy int);
INSERT dbo.TwoRows DEFAULT VALUES;
INSERT dbo.TwoRows DEFAULT VALUES;
  • 然后创建一个视图,其中包含 未通过 您的约束的行,但 cross-join 它具有此 table。此视图将永远不会包含任何行。
CREATE VIEW dbo.CheckBirthDateOfPlant
WITH SCHEMABINDING  -- must be schame-bound
AS
SELECT 1 AS dummy
FROM dbo.Maintenance m
JOIN dbo.Plant p ON p.Id = m.PlantId
CROSS JOIN dbo.TwoRows
WHERE m.MaintenanceDate < p.birthDate;
  • 然后在该视图上创建索引:
CREATE UNIQUE CLUSTERED INDEX CX_CheckBirthDateOfPlant
  ON dbo.CheckBirthDateOfPlant (dummy);

现在,每当尝试插入 更新未满足约束的行时,服务器将尝试维护此索引视图。它会将行馈送到视图的连接中,然后 cross-join 它与 TwoRows。这留下了两行,它们具有相同的 dummy 值,因此不符合唯一性。 insert/update 因此完全回滚。


如果您真的想要将此作为触发器,您现有的代码存在很多问题。

  • INSTEAD OF 触发器可能难以管理,例如,如果基础 table 发生变化,则需要对其进行修改。您应该改用 AFTER 触发器。
  • 您可以抛出异常并阻止插入。
  • 您还应该防止无效更新。
  • 您必须考虑触发器 table 中的多个(或零个!)行。
  • 标量函数真的很慢,在这种情况下是不必要的。
CREATE OR ALTER TRIGGER TrgCheckBirthDateOfPlant
ON Maintenance
AFTER INSERT, UPDATE
AS  

SET NOCOUNT ON;

IF EXISTS (SELECT 1
    FROM inserted i
    JOIN Plant p ON p.Id = i.PlantId
    WHERE i.MaintenanceDate < p.birthDate
)
    THROW 50001, 'MaintenanceDate cannot be < p.birthDate', 0;