2017-08-11 1 views
1

Ich habe eine Tabelle für Bücher, bookcategories und KategorieWie Einschränkung hinzuzufügen, in SQL zum Löschen

Es sieht aus wie dieses

Book 
    Id uniqueidentifier, 
    Title varchar(255), 
    Author varchar(255), 
    Deleted datetime 

BookCategory 
    BookId 
    CategoryId 

Category 
    Id uniqueidentifier 
    Name varchar(255) 
    Deleted datetime 

Ich möchte eine Einschränkung schreiben, das von dem Löschen eines Buches verhindern , wenn Kategorie für dieses Buch existiert

Bedeutung, wenn ein Buch (sagen wir Harry Potter) hat eine Kategorie von Fiktion zum Beispiel, Tabelle BookCategory würde beide IDs natürlich enthalten. Wenn ein Benutzer ein Buch mit einer bestimmten Kategorie löschen möchte, kann er dies nicht tun.

Kann mir some1 helfen?

PS Wenn ich Elemente lösche, lösche ich sie nicht wirklich, sondern setze Eigenschaft auf datetime gelöscht.

+2

„, wenn ich Artikel löschen, ich nicht, sie tatsächlich löschen, sondern setzen Eigenschaft gelöscht Terminzeit." Dann müssen Sie einen 'UPDATE' Trigger verwenden; Einschränkungen werden dir nicht helfen. –

+0

Was meinst du mit Update-Trigger? @ JeroenMostert – aiden87

+0

Ich werde meine 5 Cent hinzufügen.Was @JeroenMostert bedeutet, dass Sie den Datensatz nicht physisch löschen, so wird der Fremdschlüssel (der von den Leuten vorgeschlagen wird) auf diese Weise nicht helfen und der einzige Weg, den Sie erreichen können, besteht darin, TRIGGER nicht mit CONSTRAINT zu verwenden. Dort müssen Sie die Logik implementieren, die prüft, ob Datensätze vorhanden sind, die sie verwenden. –

Antwort

2

Da Sie weichen Löschungen durchführen möchten, können Sie erreichen, was Sie wollen, indem Sie einige zusätzliche Helfer Hinzufügen von Spalten und Fremdschlüssel:

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) 
) 

Hoffentlich können Sie sehen, Wie die Fremdschlüssel sicherstellen, dass die _DelXRef Spalten in den referenzierten Tabellen immer 0 bleiben müssen, und so ist es nicht möglich, Deleted auf einen anderen Wert als NULL zu setzen, während die Zeile aus der Tabelle BookCategories referenziert wird.

(An dieser Stelle ist der „Original“ Fremdschlüssel, FK_BookCategories_Books und FK_BookCategories_Categories erscheint überflüssig sein. Ich ziehe sie in dem Modell zu halten, die wirklichen FK Beziehungen zu dokumentieren. Ich bin auch meine eigene Konvention von prefixing Objekten mit mit _, wo es nicht beabsichtigt, dass sie den Benutzer der Datenbank verwendet werden - sie existieren einfach DRI zu erlauben, zu vollstreck)

+0

Kudos für irgendwie funktioniert es mit Einschränkungen sowieso. Ich bin etwas am Zaun darüber, ob die allgemeine Schlechtigkeit der Auslöser wirklich die Phantomspalten, Extraindexe und allgemeine Nichtoffensichtlichkeit dieser Lösung überwiegt, aber es ist eine Lösung. –

+0

@JeroenMostert - Wenn ein Trigger vorübergehend deaktiviert wird, können falsche Daten in den Tabellen angezeigt werden. Ich bevorzuge eine Lösung mit Constraints/Indizes/berechneten Spalten, wenn möglich, denn wenn sie aktuell überprüft werden, wissen Sie, dass das, was sie durchsetzen wollen *, tatsächlich in den Daten erzwungen wird. –

+0

Vergessen Sie nicht, dass Constraints ebenso einfach zu deaktivieren sind, und es zu vergessen, "WITH CHECK" zu verwenden, ist leider sehr einfach (da es die Standardeinstellung ist), so dass Sie immer noch mit inkonsistenten Daten enden können. Einfache Einschränkungen sind Triggern offensichtlich überlegen; dieser Fall ist ein wenig mehr zu bevorzugen. –

4

DIESE BEANTWORTET DIE ORIGINALFRAGE VOR EDITS.

Wenn Sie einen Fremdschlüssel suchen:

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

standardmäßig eine solche Einschränkung wird nicht zulassen, dass Sie ein Buch löschen, die eine Kategorie hat. Sie können über cascading Optionen lernen, um mehr Genauigkeit im Verhalten zu bieten. Der Standardwert, wenn Sie keine kaskadierende Aktion bereitstellen, ist ON DELETE NO ACTION, was bedeutet, dass die Zeile in Book nicht gelöscht wird.

+0

was meinst du mit "der standard ist auf löschen keine aktion". Standardwert der obigen Einschränkung? – aiden87

+0

Es gibt ein kleines Update zu der Frage, die Sie vielleicht sehen möchten. –

+0

er schrieb: "Wenn ich Elemente lösche, lösche ich sie nicht wirklich, aber setze Eigenschaft, die auf datetime gelöscht wird" so wird dort nicht funktionieren. Jeroen kommentierte mit dem richtigen Vorschlag –

-2

Erstellen Sie einen Primärschlüssel für die ID in der Tabelle Kategorie und verknüpfen Sie sie mit der Tabelle bookcategory (KategorieID) mit einem Fremdschlüssel.Dies wird sicherstellen, dass Sie nicht eine ID ohne Verweis auf die Elterntabelle Kategorie zur gleichen Zeit hinzufügen, die Sie nicht löschen können Datensätze aus der Kategorietabelle.

+0

danke, aber das ist nicht das, was ich suchte – aiden87

0

Wenn ich verstehe, was Sie suchen, ist ein Update auf die Tabelle zu beschränken, um einen Datensatz als "Gelöscht" zu markieren. Eine Einschränkung für die Tabelle kann dies nicht, aber Sie können dies über einen Aktualisierungstrigger erreichen. Darüber hinaus sollten Sie versuchen, diese Logik in Ihrer App zu behandeln. Lesen Sie diese Frage, die ähnlich wie bei Ihnen ist: Can an SQL constraint be used to prevent a particular value being changed when a condition holds?

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' Trigger sind schwerer zu pflegen als' AFTER' Trigger, da sie bei jeder Änderung der Tabellenstruktur neu geschrieben werden müssen. Ein "AFTER" würde hier genauso gut funktionieren - ein Fehler, der beim Trigger ausgelöst wird, führt zum Zurücksetzen des ursprünglichen Updates. –

Verwandte Themen