创建检查另一个日期的触发器 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;
我必须编写一个触发器来检查 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;