Você pode adicionar o valor do id ao final do nome quando um registro é excluído, então quando alguém exclui o id 3, o nome se torna Thingy3_3 e, quando ele exclui o id 100, o nome se torna Thingy3_100. Isso permitiria que você criasse um índice composto exclusivo no nome e nos campos excluídos, mas você teria que filtrar a coluna de nome sempre que exibi-la e remover o id do final do nome.
Talvez uma solução melhor seja substituir sua coluna excluída por uma coluna delete_at do tipo DATETIME. Você pode manter um índice exclusivo em nome e excluído em, com um registro não excluído com um valor nulo no campo delete_at. Isso impediria a criação de vários nomes em um estado ativo, mas permitiria excluir o mesmo nome várias vezes.
Obviamente, você precisa fazer um teste ao recuperar um registro para garantir que não haja nenhuma linha com o mesmo nome e um campo null delete_at antes de permitir o un-delete.
Você pode realmente implementar toda essa lógica no banco de dados usando um gatilho INSTEAD-OF para a exclusão. Esse acionador não exclui registros, mas atualiza a coluna delete_at quando você exclui um registro.
O código de exemplo a seguir demonstra isso
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
A seleção desta consulta retorna o seguinte
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
Portanto, dentro do seu código, você pode excluir registros usando uma exclusão normal e deixar o gatilho cuidar dos detalhes. O único problema possível (que eu pude ver) era que a exclusão de registros já excluídos poderia resultar em linhas duplicadas, daí a condição no gatilho para não atualizar o campo delete_at em uma linha já excluída.