我在SQL上非常糟糕,所以我需要有人检查我的触发器查询并告诉我它是否解决了问题以及它是否可以接受。这些要求有点令人费解,所以请耐心等待。
假设我有一个像这样声明的表:
CREATE TABLE Documents
(
id int identity primary key,
number1 nvarchar(32),
date1 datetime,
number2 nvarchar(32),
date2 datetime
);
对于此表,必须遵守以下约束:
是的,存在标准化不佳的问题,但我对此无能为力。
据我所知,我无法在数字日期对上编写唯一索引来检查SQL Server 2005中的某些值是否为空。因此,我尝试使用触发器验证约束。
最后一个要求 - 触发器应该没有自己的插入,只有验证检查。这就是我想出的:
CREATE TRIGGER validate_requisite_uniqueness
ON [Documents]
FOR INSERT, UPDATE
AS
BEGIN
DECLARE @NUMBER1 NVARCHAR (32)
DECLARE @DATE1 DATETIME
DECLARE @NUMBER2 NVARCHAR (32)
DECLARE @DATE2 DATETIME
DECLARE @DATETEXT VARCHAR(10)
DECLARE inserted_cursor CURSOR FAST_FORWARD FOR SELECT number1, date1, number2, date2 FROM Inserted
IF NOT EXISTS (SELECT * FROM INSERTED)
RETURN
OPEN inserted_cursor
FETCH NEXT FROM inserted_cursor into @NUMBER1, @DATE1, @NUMBER2, @DATE2
WHILE @@FETCH_STATUS = 0
BEGIN
IF (@NUMBER1 IS NULL OR @DATE1 IS NULL)
BEGIN
IF (@NUMBER2 IS NULL OR @DATE2 IS NULL)
BEGIN
ROLLBACK TRANSACTION
RAISERROR ('Either the first or the second number-date pair should be filled.', 10, 1)## Heading ##
END
END
IF (@NUMBER1 IS NOT NULL AND @DATE1 IS NOT NULL)
BEGIN
IF ((SELECT COUNT(*) FROM Documents WHERE number1 = @NUMBER1 AND date1 = @DATE1) > 1)
BEGIN
ROLLBACK TRANSACTION
SET @DATETEXT = CONVERT(VARCHAR(10), @DATE1, 104)
RAISERROR ('A document with the number1 ''%s'' and date1 ''%s'' already exists.', 10, 1, @NUMBER1, @DATETEXT)
END
END
ELSE IF (@NUMBER2 IS NOT NULL AND @DATE2 IS NOT NULL) /*the DATE2 check is redundant*/
BEGIN
IF ((SELECT COUNT(*) FROM Documents WHERE number2 = @NUMBER2 AND date2 = @DATE2) > 1)
BEGIN
ROLLBACK TRANSACTION
SET @DATETEXT = CONVERT(VARCHAR(10), @DATE2, 104)
RAISERROR ('A document with the number2 ''%s'' and date2 ''%s'' already exists.', 10, 1, @NUMBER2, @DATETEXT)
END
END
FETCH NEXT FROM inserted_cursor
END
CLOSE inserted_cursor
DEALLOCATE inserted_cursor
END
请告诉我这个解决方案的写作和效率如何。 我可以提出几个问题:
感谢。
修改
使用检查约束和索引视图的解决方案,基于Damien_The_Unbeliever的答案:
CREATE TABLE dbo.Documents
(
id int identity primary key,
number1 nvarchar(32),
date1 datetime,
number2 nvarchar(32),
date2 datetime,
constraint CK_Documents_AtLestOneNotNull CHECK (
(number1 is not null and date1 is not null) or
(number2 is not null and date2 is not null)
)
);
go
create view dbo.UniqueDocuments
with schemabinding
as
select
CASE WHEN (number1 is not null and date1 is not null)
THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT)
END as first_pair_filled,
CASE WHEN (number1 is not null and date1 is not null)
THEN number1
ELSE number2
END as number,
CASE WHEN (number1 is not null and date1 is not null)
THEN date1
ELSE date2
END as [date]
from
dbo.Documents
go
create unique clustered index IX_UniqueDocuments on dbo.UniqueDocuments(first_pair_filled,number,[date])
go
答案 0 :(得分:1)
我会避开触发器,并使用检查约束和indexed view:
CREATE TABLE dbo.Documents
(
id int identity primary key,
number1 nvarchar(32),
date1 datetime,
number2 nvarchar(32),
date2 datetime,
constraint CK_Documents_AtLestOneNotNull CHECK (
(number1 is not null and date1 is not null) or
(number2 is not null and date2 is not null)
)
);
go
create view dbo.UniqueDocuments
with schemabinding
as
select
COALESCE(number1,number2) as number,
COALESCE(date1,date2) as [date]
from
dbo.Documents
go
create unique clustered index IX_UniqueDocuments on dbo.UniqueDocuments(number,[date])
go
其优点在于,虽然有一些类似触发器的"由于索引视图的行为,它经过良好测试的代码已经深入集成到SQL Server中。
答案 1 :(得分:0)
我会使用这个逻辑(我没有输入它,因为它需要很长时间),并且肯定在IF EXISTS()语句中使用SELECT 1 FROM ...因为它有助于提高性能。同样删除像marc_s所说的游标。
CREATE TRIGGER trg_validate_requisite_uniqueness
ON dbo.[Documents]
AFTER INSERT, UPDATE
AS
DECLARE @Number1 NVARCHAR(100) = (SELECT TOP 1 number1 FROM dbo.Documents ORDER BY Id DESC)
DECLARE @Date1 DATETIME = (SELECT TOP 1 date1 FROM dbo.Documents ORDER BY Id DESC)
DECLARE @Number2 NVARCHAR(100) = (SELECT TOP 1 number2 FROM dbo.Documents ORDER BY Id DESC)
DECLARE @Date2 DATETIME = (SELECT TOP 1 date2 FROM dbo.Documents ORDER BY Id DESC)
DECLARE @DateText NVARCHAR(100)
IF EXISTS (SELECT 1 FROM dbo.Documents AS D
INNER JOIN INSERTED AS I ON D.id = I.id WHERE I.Number1 IS NULL AND I.number2 IS NULL)
BEGIN
ROLLBACK TRANSACTION
RAISERROR ('Either the first or the second number pair should be filled.', 10, 1)
END
ELSE IF EXISTS (SELECT 1 FROM dbo.Documents AS D
INNER JOIN INSERTED AS I ON D.id = I.id WHERE I.Date1 IS NULL AND I.Date2 IS NULL)
BEGIN
ROLLBACK TRANSACTION
RAISERROR ('Either the first or the second date pair should be filled.', 10, 1)
END
ELSE IF EXISTS (SELECT 1 FROM dbo.Documents AS D
GROUP BY D.number1, D.date1 HAVING COUNT(*) >1
)
BEGIN
ROLLBACK TRANSACTION
SET @DateText = (SELECT CONVERT(VARCHAR(10), @Date1, 104))
RAISERROR ('Cannot have duplicate values', 10, 1, @Number1, @DateText )
END