2009-04-21 4 views
5

데이터베이스 항목이 삭제되지 않지만 삭제 된 것으로 표시된 프로젝트에서 작업하고 있습니다. 이런 식으로 뭔가가 :데이터베이스 제약 조건을 사용하고 삭제 대신 삭제로 표시 만하려면 어떻게해야합니까?

id name  deleted 
--- ------- -------- 
1 Thingy1 0 
2 Thingy2 0 
3 Thingy3 0 

나는 name 컬럼에 UNIQUE 제약 조건 같은 것을 정의 할 수 있도록하고 싶습니다. 쉽지, 그렇지?

"Thingy3"이 삭제되고 새 것으로 만들어 졌다고 가정 해 봅니다 (수년 후). 우리는 다음을 얻습니다 :

id name  deleted 
--- ------- -------- 
1 Thingy1 0 
2 Thingy2 0 
3 Thingy3 1 
... 
100 Thingy3 0 

사용자의 관점에서, 그는 항목을 삭제하고 새 것을 만들었습니다. 파일을 삭제하고 새 파일을 만드는 것과 비슷합니다. 따라서 새 항목이 관련이 없으며 이전 항목에 연결된 모든 데이터에 연결되지 않았 음이 분명합니다.

DB는 id 만 취급하므로 새 항목의 값은 id이 3이 아니므로 완전히 처리되었으므로이 값은 완전히 다릅니다.

사용자가 생성하지 못하도록하려는 경우 어려움이 발생합니다. "Thingy3"항목. deleted으로 표시되지 않은 항목 만 보았던 UNIQUE 제약 조건이있는 경우 한 가지 문제가 해결되었을 것입니다.

그래서

, 어떻게 제약의 종류를 정의 할 수 있습니다 (물론, 그때 ... 누군가가 삭제의 취소를 수행 할 때 무슨 일을 처리해야 할 것이다)?

CREATE UNIQUE INDEX [MyINDEX] ON [TABLENAME] ([NAME] , [DELETED]) 

바와 같이 SteveWeet 지적

+2

이 말은 소프트 삭제라고합니다. http://databaserefactoring.com/IntroduceSoftDelete.html – cherouvim

+1

이름을 알고있는 것을 잘 알고 있습니다. 감사! – scraimer

답변

11

레코드가 삭제되면 이름 끝에 id 값을 추가 할 수 있습니다. 따라서 누군가가 id 3을 삭제하면 이름이 Thingy3_3이되고 id 100이 삭제되면 이름이 Thingy3_100이됩니다. 이렇게하면 이름 및 삭제 된 필드에 고유 한 복합 인덱스를 만들 수 있지만 표시 할 때마다 이름 열을 필터링하고 이름 끝에있는 ID를 제거해야합니다.

아마 더 나은 해결책은 삭제 된 열을 DATETIME 유형의 deleted_at 열로 바꾸는 것입니다. 그런 다음 삭제 된 필드에 null 값이있는 삭제되지 않은 레코드와 함께 name에 고유 인덱스를 유지하고 삭제할 수 있습니다. 이렇게하면 활성 상태에서 여러 이름을 만들지 못하지만 동일한 이름을 여러 번 삭제할 수 있습니다.

삭제 취소를 허용하기 전에 동일한 이름의 행이없고 null deleted_at 필드가 없는지 확인하기 위해 레코드를 삭제 취소 할 때 테스트해야합니다.

실제로 삭제를 위해 INSTEAD-OF 트리거를 사용하여 데이터베이스 내에이 모든 논리를 구현할 수 있습니다. 이 트리거는 레코드를 삭제하지 않지만 대신 레코드를 삭제할 때 deleted_at 열을 업데이트합니다.

다음 예제 코드는이 쿼리에서 선택이

CREATE TABLE swtest ( 
    id   INT IDENTITY, 
    name  NVARCHAR(20), 
    deleted_at DATETIME 
) 
GO 
CREATE TRIGGER tr_swtest_delete ON swtest 
INSTEAD OF DELETE 
AS 
BEGIN 
    UPDATE swtest SET deleted_at = getDate() 
    WHERE id IN (SELECT deleted.id FROM deleted) 
    AND deleted_at IS NULL  -- Required to prevent duplicates when deleting already deleted records 
END 
GO 

CREATE UNIQUE INDEX ix_swtest1 ON swtest(name, deleted_at) 

INSERT INTO swtest (name) VALUES ('Thingy1') 
INSERT INTO swtest (name) VALUES ('Thingy2') 
DELETE FROM swtest WHERE id = SCOPE_IDENTITY() 
INSERT INTO swtest (name) VALUES ('Thingy2') 
DELETE FROM swtest WHERE id = SCOPE_IDENTITY() 
INSERT INTO swtest (name) VALUES ('Thingy2') 

SELECT * FROM swtest 
DROP TABLE swtest 

를 반환 보여

 
id  name  deleted_at 
1  Thingy1 NULL 
2  Thingy2 2009-04-21 08:55:38.180 
3  Thingy2 2009-04-21 08:55:38.307 
4  Thingy2 NULL 

을 다음 그래서 코드 내에 삭제 정상를 사용 기록을 삭제하고 트리거가 알아서하도록 할 수 있습니다 세부 사항들. 유일하게 가능한 문제 (내가 볼 수있는 것)는 이미 삭제 된 레코드를 삭제하면 중복 행이 생길 수 있으므로 이미 삭제 된 행의 deleted_at 필드를 업데이트하지 않는 트리거 조건입니다.

+0

내가 직접 내 응용 프로그램을 고려하고 있었기 때문에 투표했습니다. 어떤 단점을 생각하려고 노력하고 있지만, 지금까지는 충분히 견고한 것 같습니다. – belugabob

+0

"deleted_at"아이디어가 마음에 듭니다. – scraimer

0
SQLServer2005 확실

하지,하지만 당신은 정의 할 수 있습니다 복합 constrainst/인덱스, 이것은 단지가 만든 두 번/삭제 할 수 있습니다.

+0

이렇게하면 'Thingy3'을 한 번만 삭제할 수 있습니다. 만들 수 없거나 삭제하고 다시 생성 한 다음 다시 삭제할 수 없습니다. –

+0

좋은 점은 N 개의 업데이트가 허용되지 않는다는 것입니다. –

1

예를 들어, 삭제 된 이름에 잘못된 문자 (*)를 추가 할 수 있습니다. 그러나 삭제 된 항목을 삭제 취소하는 데 여전히 문제가 있습니다. 따라서 더 좋은 생각은 삭제 된 경우에도 이중 이름을 금지하는 것입니다.

시간이 경과하면 삭제 된 레코드를 지우거나 별도의 테이블로 옮길 수 있습니다.

+0

사용자가 입력 한 데이터를 수정하는 것은 데이터가 불법적 인 문자로 끝나는 지 반드시 예측할 수 없으므로 항상 문제가 될 것입니다. 틀림없이 캐릭터는 '삭제 된'레코드에만 추가되므로 예측 가능성의 특정 요소가 도입됩니다. 어쨌든, 나는 Undeletes를 지원하기 때문에 Steve Weet의 솔루션을 선호한다. – belugabob

0

(name, deleted)에 고유 제한 조건을 만듭니다. 그러나 이름 당 하나만 삭제할 수 있습니다.

ANSI-92에서가 아니라 MS의 SQLServer에 그 작품에 대한 명백한 주위에 작업 : 열 ID (INT), 필터 (NVARCHAR), 삭제와 [테스트]라는 간단한 테이블의 경우 https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=299229

+0

"삭제 된"열은 충돌 가능성이 적은 항목이어야합니다 (부울 없음!). 동일한 이름의 항목이 여러 개있을 수 있습니다. – deamon

1

(비트)

ALTER TABLE [dbo].[Test] ADD 
    CONSTRAINT [DF_Test_Deleted] DEFAULT (0) FOR [Deleted], 
    CONSTRAINT [IX_Test] UNIQUE NONCLUSTERED 
    (
     [filter], 
     [Deleted] 
    ) ON [PRIMARY] 
+0

이 접근법은 레코드가 '삭제'될 때 동일한 '필터'값을 가진 레코드가 추가되고이 레코드를 '삭제'하려고 시도 할 때 문제가됩니다. 제약 조건을 위반하면됩니다. – belugabob

1

고유 이름 뒤에 임의의 해시를 추가하십시오. 쉽게 되돌릴 수있는 것. 밑줄이나 다른 문자로 분리 가능.

덧글 후 편집 : 밑줄과 현재 시간 스탬프를 간단히 추가 할 수 있습니다.

+0

Brilliant! 젠장, 나는 심지어 "Thingy3 (1/1/2000 # 1에서 삭제됨)"과 같은 이름으로 사용자에게 추가 정보를 바꿀 수 있습니다! – scraimer

2

복합 고유 제한 조건의 문제점은 삭제 된 동일한 이름을 가진 여러 레코드를 가질 수 없다는 것입니다. 즉, 세 번째 레코드를 삭제하면 시스템이 중단됩니다. 이론적으로 중복 된 상황이 발생할 수 있기 때문에 이름에 내용을 추가하지 않는 것이 좋습니다. 또한 이렇게함으로써 데이터베이스의 데이터가 손상되고 데이터에 숨겨진 비즈니스 로직이 추가됩니다.

데이터베이스 전체에서 가능한 유일한 해결책은 삽입/업데이트 된 데이터가 유효한지 확인하는 트리거를 추가하는 것입니다. 또한 데이터베이스 외부의 검사를 코드에 넣을 수도 있습니다.

3

"휴지통"테이블을 사용하는 것이 좋습니다. 동일한 테이블에 플래그가있는 이전 레코드를 유지하는 대신 자체 제약 조건을 사용하여 자체 테이블로 이동하십시오. 예를 들어, 활성 테이블에는 UNIQUE 제약 조건이 있지만 휴지통 테이블에서는 그렇지 않습니다.

+0

ORM을 사용할 때 충돌이 발생할 수 있습니다 (여기에 해당되는지 여부는 알 수 없음). – deamon

1

삭제 된 열 대신에 end_date 열을 사용하십시오. 사용자가 레코드를 삭제하면 end_date 열에 현재 날짜를 추가합니다. end_date 열이 NULL 인 레코드는 현재 레코드입니다. name 및 end_date 열에 대한 고유 제한 조건을 정의하십시오. 이 제한 조건으로 인해 유효한 레코드 이름이 복제되는 시나리오가 생기지 않습니다. 사용자가 레코드의 삭제를 원할 때마다 end_date 컬럼을 null로 설정해야하며, 이것이 고유 제한 조건을 위반하면 사용자에게 같은 이름이 이미 있음을 나타내는 메시지 사용자를 표시합니다.

+0

스티브 위트 (Steve Weet)는 이미 이렇게 말했습니다. http://stackoverflow.com/questions/771197/what-to-do-when-i-want-to-use-database-constraints-but-only-mark-as-deleted- inste/771337 # 771337 – scraimer

1

제약 조건 유형은 테이블 수준 CHECK 제약 조건, 즉 테이블에 대해 NOT EXISTS (또는 이에 상응하는 값)을 테스트하는 하위 쿼리로 구성된 CHECK 제약 조건입니다.

CREATE TABLE Test 
(
    ID INTEGER NOT NULL UNIQUE, 
    name VARCHAR(30) NOT NULL, 
    deleted INTEGER NOT NULL, 
    CHECK (deleted IN (0, 1)) 
); 

ALTER TABLE Test ADD 
    CONSTRAINT test1__unique_non_deleted 
     CHECK 
     (
     NOT EXISTS 
     (
      SELECT T1.name 
       FROM Test AS T1 
      WHERE T1.deleted = 0 
      GROUP 
       BY T1.Name 
      HAVING COUNT(*) > 1 
     ) 
    ); 

INSERT INTO Test (ID, name, deleted) VALUES (1, 'Thingy1', 0) 
; 
INSERT INTO Test (ID, name, deleted) VALUES (2, 'Thingy2', 0) 
; 
INSERT INTO Test (ID, name, deleted) VALUES (3, 'Thingy3', 1) 
; 
INSERT INTO Test (ID, name, deleted) VALUES (4, 'Thingy3', 1) 
; 
INSERT INTO Test (ID, name, deleted) VALUES (5, 'Thingy3', 0) 
; 
INSERT INTO Test (ID, name, deleted) VALUES (6, 'Thingy3', 0) 
; 

마지막 INSERT (ID = 6)가 제한 물지하게되고 INSERT는 실패 할 것이다. Q.E.D.

... ooh, 거의 언급 할 필요가 없습니다. SQL Server는 하위 쿼리가 포함 된 CHECK 제약 조건을 아직 지원하지 않습니다 (ACE/JET, a.k.a.에서 위 테스트). 일 수 있습니다. FUNCTION은 행 단위로 SQL Server 테스트 제약 조건 (David Portas' Blog 참조)으로 인해 안전하지 않다고 읽었습니다. 이 전체 SQL-92 기능이 SQL Server에서 지원 될 때까지, 필자가 선호하는 해결 방법은 트리거에서 동일한 논리를 사용하는 것입니다.

관련 문제