7

データベース アイテムが削除されず、削除済みとしてマークされているだけのプロジェクトで作業しています。このようなもの:

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

列に UNIQUE 制約のようなものを定義できるようにしたいと考えていnameます。簡単そうですよね?

「Thingy3」が削除され、新しいものが作成されるシナリオを想像してみましょう (おそらく数年後)。我々が得る:

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

ユーザーの観点から見ると、彼はアイテムを削除して新しいアイテムを作成しました。ファイルを削除して新しいファイルを作成するのと同じです。したがって、新しいアイテムが古いアイテムに関連するデータとは無関係であり、関連付けられていないことは明らかです。

DB は のみを気にするため、これは既に処理されていidます。また、新しいアイテムの はid3 ではなく 100 であるため、それらはまったく異なります。

ユーザーが別の「Thingy3」アイテムを作成できないようにしたい場合、問題が発生します。マークされていないアイテムのみを見る UNIQUE 制約があれば、deleted1 つの問題を解決できたはずです。

(もちろん、誰かが削除を元に戻したときに何が起こるかを処理する必要があります...)

では、そのような制約をどのように定義できますか?

4

10 に答える 10

15

レコードが削除されたときに名前の末尾に id 値を追加できます。そのため、誰かが ID 3 を削除すると名前が Thingy3_3 になり、ID 100 を削除すると名前が Thingy3_100 になります。これにより、名前と削除されたフィールドに一意の複合インデックスを作成できますが、名前列を表示するたびにフィルター処理し、名前の末尾から id を削除する必要があります。

おそらく、削除された列をDATETIME型のdeleted_at列に置き換えることがより良い解決策になるでしょう。次に、削除されていないレコードのdeleted_atフィールドにnull値があり、nameとdeleted atに一意のインデックスを維持できます。これにより、アクティブな状態で複数の名前を作成することはできなくなりますが、同じ名前を複数回削除することができます。

削除を取り消す前に、同じ名前の行がなく、deleted_at フィールドが null であることを確認するために、レコードの削除を取り消すときに明らかにテストを行う必要があります。

実際には、削除に 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名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 ヌル

したがって、コード内で通常の削除を使用してレコードを削除し、トリガーに詳細を任せることができます。唯一の可能な問題 (私が見ることができた) は、既に削除されたレコードを削除すると行が重複する可能性があることでした。したがって、トリガーの条件は、既に削除された行の deleted_at フィールドを更新しません。

于 2009-04-21T06:48:55.263 に答える
3

「ごみ箱」テーブルの使用を検討する価値があるかもしれません。古いレコードをフラグを付けて同じテーブルに保持する代わりに、独自の制約を持つ独自のテーブルに移動します。たとえば、アクティブなテーブルでは名前に UNIQUE 制約がありますが、ごみ箱テーブルにはありません。

于 2009-04-21T08:09:30.377 に答える
2

複合一意制約の問題は、同じ名前の複数のレコードを削除できないことです。これは、3 番目のレコードを削除すると、システムが壊れることを意味します。理論的には、重複する状況が発生する可能性があるため、名前に何かを追加することはお勧めしません。また、そうすることで、基本的にデータベース内のデータが破損するだけでなく、データ自体に不可解なビジネス ロジックが追加されます。

データベース全体で可能な唯一の解決策は、挿入/更新されたデータが有効であることを確認するトリガーを追加することです。チェックをデータベースの外部、コードに入れることも可能です。

于 2009-04-21T06:52:22.213 に答える
1

たとえば、削除された名前に不正な文字 (*) を追加できます。ただし、削除済みアイテムの削除を取り消すにはまだ問題があります。したがって、削除された場合でも二重名を禁止することをお勧めします。

削除されたレコードは、一定時間後に消去できます (または別のテーブルに移動できます)。

于 2009-04-21T06:05:43.903 に答える
1

ID(int)、Filter(nvarchar)、Deleted(bit) 列を持つ [Test] という単純なテーブルの場合

ALTER TABLE [dbo].[Test] ADD 
    CONSTRAINT [DF_Test_Deleted] DEFAULT (0) FOR [Deleted],
    CONSTRAINT [IX_Test] UNIQUE  NONCLUSTERED 
    (
        [filter],
        [Deleted]
    )  ON [PRIMARY] 
于 2009-04-21T06:12:08.147 に答える
1

必要な制約のタイプは、テーブル レベルの制約です。つまり、テーブルをテストする(または同等の)CHECKサブクエリで構成される CHECK 制約です。NOT EXISTS

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失敗します。QED

...ああ、言い忘れそうになりました: SQL Server は、サブクエリを含む制約をまだサポートしていませんCHECK(私は ACE/JET で上記をテストしました)。を使用することもできFUNCTIONますが、 SQL Server が行ごとに制約をテストするため、これは安全ではないと読んだことがあります ( David Portas のブログを参照してください)。この完全な SQL-92 機能が SQL Server でサポートされるまで、私の推奨する回避策は、トリガーで同じロジックを使用することです。

于 2009-04-21T13:06:56.883 に答える
1

削除された列の代わりに end_date 列を使用します。ユーザーがレコードを削除すると、end_date 列に現在の日付が追加されます。end_date 列が NULL であるすべてのレコードは、現在のレコードです。name と end_date の 2 つの列に一意の制約を定義します。この制約により、有効なレコード名が重複するシナリオはありません。ユーザーがレコードの削除を取り消したい場合はいつでも、end_date 列を null に設定する必要があります。これが一意の制約に違反する場合は、同じ名前が既に存在するというメッセージをユーザーに表示します。

于 2009-04-21T08:22:05.900 に答える
1

一意の名前の後にランダム ハッシュを追加します。簡単に元に戻せるもの。アンダースコアまたはその他の文字で区切られている可能性があります。

コメントの後に編集: アンダースコアと現在のタイムスタンプを追加するだけです。

于 2009-04-21T06:12:39.393 に答える
0

SQLServer2005についてはわかりませんが、複合制約/インデックスを定義できます

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

SteveWeet が指摘したように、これにより、削除/作成を 2 回しか行うことができなくなります。

于 2009-04-21T06:01:43.600 に答える
0

(name, deleted) にユニーク制約を作成します。ただし、これは名前ごとに 1 つしか削除できないことを意味します。

そのための明らかな回避策は、ANSI-92 では機能しますが、MS SQLServer では機能しません: https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=299229

于 2009-04-21T06:09:12.117 に答える