如何添加约束在SQL
删除我的书,bookcategories表和类别如何添加约束在SQL
它看起来像这样
Book
Id uniqueidentifier,
Title varchar(255),
Author varchar(255),
Deleted datetime
BookCategory
BookId
CategoryId
Category
Id uniqueidentifier
Name varchar(255)
Deleted datetime
我想写一个约束,这将阻止删除一本书,如果该书的类别存在
意思是,如果一本书(可以说哈利波特)有一个小说类别,例如表BookCategory将包括这两个Id的当然。如果用户想要删除具有特定类别的图书,他将无法执行此操作。
Can some1能帮助我吗?
PS当我删除项目,我没有实际删除它们,但设置属性删除datetime。
既然要执行软删除,你可以完成你想要的东西通过添加一些额外的辅助列和外键:
create table Books (
Id uniqueidentifier primary key,
Title varchar(255) not null,
Author varchar(255) not null,
Deleted datetime null,
_DelXRef as CASE WHEN Deleted is null then 0 else 1 END persisted,
constraint UQ_Books_DelXRef UNIQUE (Id,_DelXRef)
)
create table Categories (
Id uniqueidentifier primary key,
Name varchar(255) not null,
Deleted datetime null,
_DelXRef as CASE WHEN Deleted is null then 0 else 1 END persisted,
constraint UQ_Categories_DelXRef UNIQUE (Id,_DelXRef)
)
create table BookCategories (
BookId uniqueidentifier not null,
CategoryId uniqueidentifier not null,
_DelXRef as 0 persisted,
constraint FK_BookCategories_Books foreign key (BookID) references Books(Id),
constraint FK_BookCategories_Books_DelXRef foreign key (BookID,_DelXRef) references Books(Id,_DelXRef),
constraint FK_BookCategories_Categories foreign key (CategoryId) references Categories(Id),
constraint FK_BookCategories_Categories_DelXRef foreign key (CategoryId,_DelXRef) references Categories(Id,_DelXRef)
)
希望,你可以看到外键如何确保引用表中的_DelXRef
列始终保持0
,因此无法将Deleted
设置为任何非NULL值,同时从BookCategories
表中引用该行。 (现在,“原始”外键,FK_BookCategories_Books
和FK_BookCategories_Categories
似乎是多余的。我更喜欢让他们在模型中记录真正的FK关系。我也使用我自己的约定前缀对象与_
其中,它并不意味着他们可以使用到数据库的用户 - 他们只是存在,使DRI予以强制执行)
以某种方式让它使用约束无论如何工作的荣誉。对于这种解决方案,触发器的一般不良情况是否真的超过了虚幻列,额外索引和一般非显而易见的因素,我有点困惑。 –
@JeroenMostert - 如果一个触发器临时被禁用,最终可能会在表中出现错误的数据。我更喜欢使用约束/索引/计算列的解决方案,因为如果他们目前已被选中,您知道他们试图强制执行*实际上是在数据中执行的。 –
不要忘记,约束同样容易禁用,并且忘记使用WITH CHECK来重新启用约束不幸的是很容易做到(因为它是默认的),所以你仍然可能以不一致的数据结束。简单的限制显然优于触发器;这种情况有点偏好。 –
这回答了编辑前的原始问题。
你正在寻找一个外键约束:
alter table BookCategory add constraint fk_BookCategory_Book
foreign key (BookId) references Book(Id);
默认情况下,这样的限制不会允许你删除一本书,有一个类别。您可以了解cascading
选项以提供更精确的行为。不提供级联操作时的默认值是ON DELETE NO ACTION
,这意味着Book
中的行不会被删除。
你的意思是“默认是删除不行动”。以上约束的默认值? – aiden87
关于您可能想要查看的问题,有一个小小的更新。 –
他写道:“当我删除项目时,我实际上并未删除它们,但将属性设置为删除日期时间”,因此无法在此工作。 Jeroen评论了正确的建议 –
在表类别中为id创建一个主键,并用外键将它与表bookcategory(categoryid)相关联。这将确保您不会在不能删除同时不引用父表类别的情况下添加一个id类别表中的记录也是如此。
谢谢,但这不是我正在寻找 – aiden87
如果我明白,你要做的是限制对表格的更新以将记录标记为“已删除”。表上的约束不能做到这一点,但你可以通过更新触发器来完成。此外,你应该尝试在你的应用程序中处理这种逻辑。阅读这问那与你相似:Can an SQL constraint be used to prevent a particular value being changed when a condition holds?
CREATE TABLE Book
(
Id UNIQUEIDENTIFIER
, Title VARCHAR(255)
, Author VARCHAR(255)
, Deleted DATETIME
);
CREATE TABLE BookCategory
(
BookId UNIQUEIDENTIFIER
, CategoryId UNIQUEIDENTIFIER
);
CREATE TABLE Category
(
Id UNIQUEIDENTIFIER
, Name VARCHAR(255)
, Deleted DATETIME
);
INSERT INTO dbo.Book ( Id
, Title
, Author
, Deleted
)
VALUES ( 'BCF8DE45-D26F-43EE-82CD-9975E35E51B1' -- Id - uniqueidentifier
, 'Harry Potter' -- Title - varchar(255)
, 'RK' -- Author - varchar(255)
, NULL -- Deleted - datetime
);
INSERT INTO dbo.Category ( Id
, Name
, Deleted
)
VALUES ( 'EB6E823D-DFE8-448D-B648-EAE1EFE06358' -- Id - uniqueidentifier
, 'Fantasy' -- Name - varchar(255)
, NULL -- Deleted - datetime
);
INSERT INTO dbo.Category ( Id
, Name
, Deleted
)
VALUES ( '9B53C866-0DAA-4637-8169-5B8885A2E644' -- Id - uniqueidentifier
, 'Category without books' -- Name - varchar(255)
, NULL -- Deleted - datetime
);
INSERT INTO dbo.BookCategory ( BookId
, CategoryId
)
VALUES ( 'BCF8DE45-D26F-43EE-82CD-9975E35E51B1' -- BookId - int
, 'EB6E823D-DFE8-448D-B648-EAE1EFE06358' -- CategoryId - int
);
GO
CREATE TRIGGER trg_Category_Check_Category_Usage
ON Category
INSTEAD OF UPDATE
AS
BEGIN
IF UPDATE(Deleted)
BEGIN
IF EXISTS ( SELECT *
FROM INSERTED i
JOIN dbo.BookCategory AS b ON b.CategoryId = i.Id
WHERE i.Deleted IS NOT NULL
)
THROW 60000, 'record exists', 1;
END;
UPDATE b
SET b.deleted = i.deleted
, b.Name = i.Name
FROM Category b
JOIN INSERTED i ON i.Id = b.Id;
END;
GO
UPDATE dbo.Category SET Deleted = GETDATE()WHERE Name = 'Fantasy'; --error
UPDATE dbo.Category
SET Deleted = GETDATE()
WHERE Name = 'Category without books'; --no error
'INSTEAD OF'触发器比'AFTER'触发器更难维护,因为它们必须在每次表结构变化时重写。一个'AFTER'在这里也可以做得很好 - 在触发器中引发的错误会回滚原始更新。 –
“当我删除的项目,我不真正删除它们,但设置属性删除,约会时间。”那么你将不得不使用'UPDATE'触发器;限制不会帮助你。 –
更新触发器是什么意思? @JeroenMostert – aiden87
我会加我5美分。@JeroenMostert的意思是你不是在物理上删除记录,所以外键(人们建议你使用)不会以这种方式提供帮助,唯一可以实现的方式是使用TRIGGER而不是CONSTRAINT。在那里你需要实现检查是否有任何使用它的记录的逻辑。 –