Что делать, когда я хочу использовать ограничения базы данных, но только пометить как удаленные вместо удаления?
Я работаю в проекте, где элементы базы данных не удаляются, а только помечаются как удаленные. Что-то вроде этого:--7-->
id name deleted
--- ------- --------
1 Thingy1 0
2 Thingy2 0
3 Thingy3 0
Я хотел бы иметь возможность определить что-то вроде уникального ограничения на
10 ответов
вы можете добавить значение id в конец имени при удалении записи, поэтому, когда кто-то удаляет id 3, имя становится Thingy3_3, а затем, когда они удаляют id 100, имя становится Thingy3_100. Это позволит вам создать уникальный составной индекс для полей имя и удаленные, но затем вы должны фильтровать столбец имя при его отображении и удалить идентификатор из конца имени.
возможно, лучшим решением было бы заменить удаленный столбец на столбец deleted_at типа DATETIME. Затем вы можете поддерживать уникальный индекс на name и deleted at, при этом не удаленная запись имеет нулевое значение в поле deleted_at. Это предотвратит создание нескольких имен в активном состоянии, но позволит вам удалить одно и то же имя несколько раз.
очевидно, вам нужно сделать тест при отмене записи, чтобы убедиться, что нет строки с тем же именем и нулевым полем deleted_at, прежде чем разрешить ООН-удалить.
вы можете фактически реализовать всю эту логику в базе данных, используя вместо триггера для удаления. Этот триггер не будет удалять записи, а вместо этого обновит столбец 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
select из этого запроса возвращает следующее
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 в уже удаленной строке.
возможно, стоит рассмотреть возможность использования таблицы "корзина". Вместо того чтобы хранить старые записи в одной таблице с флагом, переместите их в свою собственную таблицу со своими ограничениями. Например, в активной таблице у вас есть уникальное ограничение на имя, но в таблице корзины его нет.
проблема с составным уникальным ограничением заключается в том, что невозможно иметь несколько записей с одинаковым именем, которые удаляются. Это означает, что система сломается после удаления третьей записи. Я бы не рекомендовал добавлять материал к именам, потому что теоретически может возникнуть дублирующая ситуация. Кроме того, делая это, вы в основном искажаете данные в базе данных, а также добавляете загадочную бизнес-логику к самим данным.
единственно возможный решение, база данных в целом, заключается в добавлении триггера, который проверяет, что вставленные/обновленные данные действительны. Также можно поместить проверки вне базы данных, в код.
например, к удаленному имени можно добавить незаконный символ ( * ). Но у вас все еще есть проблемы с восстановлением удаленного элемента. Поэтому, вероятно, лучше запретить двойные имена, даже если они будут удалены.
вы можете очистить удаленные записи через некоторое время (или переместить их в отдельную таблицу).
для простой таблицы под названием [Test] со столбцами ID (int), Filter(nvarchar), Deleted(bit)
ALTER TABLE [dbo].[Test] ADD
CONSTRAINT [DF_Test_Deleted] DEFAULT (0) FOR [Deleted],
CONSTRAINT [IX_Test] UNIQUE NONCLUSTERED
(
[filter],
[Deleted]
) ON [PRIMARY]
добавить случайный хэш после уникального имени. Что - то, что легко обратимо. Возможно, отдельно с подчеркиванием или каким-то другим символом.
редактировать после комментария: вы можете просто добавить подчеркивание и текущую метку времени.
вместо удаленного столбца используйте столбец end_date. Когда пользователь удаляет запись, добавьте текущую дату в столбец end_date. Любые записи, в которых столбец end_date равен NULL, являются вашими текущими записями. Определите уникальное ограничение для двух столбцов name и end_date. Из-за этого ограничения у вас никогда не будет сценария, в котором действительное имя записи дублируется. Каждый раз, когда пользователь хочет восстановить запись, вам нужно установить столбец end_date в значение null, и если это нарушает уникальное ограничение, вы показываете сообщение пользователь для пользователя, имя которого уже существует.
тип ограничения, который вам требуется, - это уровень таблицы 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
не удастся. Д. Е. В.
...о, почти забыл упомянуть: SQL Server еще не поддерживает CHECK
ограничения, содержащие подзапрос (я тестировал выше на ACE / JET, a.к. a. ). В то время как вы мог бы использовать FUNCTION
Я прочитал, что это небезопасно из-за ограничений тестирования SQL Server по строкам (см. Блог Дэвида Портаса). Пока эта полная функция SQL-92 не будет поддерживаться в SQL Server, мой предпочтительный обходной путь-использовать ту же логику в триггере.
создать ограничение уникальности на (имя удалено). Это будет означать, что вы можете удалить только одно имя на имя.
очевидная работа вокруг это работает под ANSI-92, но не на MS SQLServer: https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=299229
Не уверен в SQLServer2005, но вы можете определить составные constrainst/индексы
CREATE UNIQUE INDEX [MyINDEX] ON [TABLENAME] ([NAME] , [DELETED])
как указал SteveWeet, это позволит вам удалить / создать только дважды.