2017-08-11 57 views
1

删除我的书,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。

+2

“当我删除的项目,我不真正删除它们,但设置属性删除,约会时间。”那么你将不得不使用'UPDATE'触发器;限制不会帮助你。 –

+0

更新触发器是什么意思? @JeroenMostert – aiden87

+0

我会加我5美分。@JeroenMostert的意思是你不是在物理上删除记录,所以外键(人们建议你使用)不会以这种方式提供帮助,唯一可以实现的方式是使用TRIGGER而不是CONSTRAINT。在那里你需要实现检查是否有任何使用它的记录的逻辑。 –

回答

2

既然要执行软删除,你可以完成你想要的东西通过添加一些额外的辅助列和外键:

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_BooksFK_BookCategories_Categories似乎是多余的。我更喜欢让他们在模型中记录真正的FK关系。我也使用我自己的约定前缀对象与_其中,它并不意味着他们可以使用到数据库的用户 - 他们只是存在,使DRI予以强制执行)

+0

以某种方式让它使用约束无论如何工作的荣誉。对于这种解决方案,触发器的一般不良情况是否真的超过了虚幻列,额外索引和一般非显而易见的因素,我有点困惑。 –

+0

@JeroenMostert - 如果一个触发器临时被禁用,最终可能会在表中出现错误的数据。我更喜欢使用约束/索引/计算列的解决方案,因为如果他们目前已被选中,您知道他们试图强制执行*实际上是在数据中执行的。 –

+0

不要忘记,约束同样容易禁用,并且忘记使用WITH CHECK来重新启用约束不幸的是很容易做到(因为它是默认的),所以你仍然可能以不一致的数据结束。简单的限制显然优于触发器;这种情况有点偏好。 –

4

这回答了编辑前的原始问题。

你正在寻找一个外键约束:

alter table BookCategory add constraint fk_BookCategory_Book 
    foreign key (BookId) references Book(Id); 

默认情况下,这样的限制不会允许你删除一本书,有一个类别。您可以了解cascading选项以提供更精确的行为。不提供级联操作时的默认值是ON DELETE NO ACTION,这意味着Book中的行不会被删除。

+0

你的意思是“默认是删除不行动”。以上约束的默认值? – aiden87

+0

关于您可能想要查看的问题,有一个小小的更新。 –

+0

他写道:“当我删除项目时,我实际上并未删除它们,但将属性设置为删除日期时间”,因此无法在此工作。 Jeroen评论了正确的建议 –

-2

在表类别中为id创建一个主键,并用外键将它与表bookcategory(categoryid)相关联。这将确保您不会在不能删除同时不引用父表类别的情况下添加一个id类别表中的记录也是如此。

+0

谢谢,但这不是我正在寻找 – aiden87

0
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 
+0

'INSTEAD OF'触发器比'AFTER'触发器更难维护,因为它们必须在每次表结构变化时重写。一个'AFTER'在这里也可以做得很好 - 在触发器中引发的错误会回滚原始更新。 –