Validando Update e instruções INSERT contra uma tabela inteira
-
06-07-2019 - |
Pergunta
Eu estou procurando a melhor maneira de ir sobre a adição de uma restrição a uma tabela que é efetivamente um índice exclusivo sobre a relação entre o registro e o resto dos registros na tabela.
Imagine a seguinte tabela descrevendo as patrulhas de vários guardas (do cenário vigia anterior)
PK PatrolID Integer
FK GuardID Integer
Starts DateTime
Ends DateTime
Começamos com uma restrição especificando que os horários de início e de término deve ser lógico:
Ends >= Starts
No entanto eu quero adicionar outro constrangimento lógico: Um específico de guarda (GuardID) não pode estar em dois lugares ao mesmo tempo, o que significa que para qualquer registro do período especificado pelo Start / extremidades devem não sobreposição com o período definido para qualquer outra patrulha pelo mesmo guarda.
Eu posso pensar de duas maneiras de tentar abordar esta:
Criar um VEZ DE INSERÇÃO gatilho. Esse gatilho, então, usar cursores para percorrer a tabela inserida, verificando cada registro. Se qualquer registro em conflito com um registro existente, um erro seria levantado. Os dois problemas que tenho com esta abordagem são: Eu não gosto de utilizar cursores em uma versão moderna do SQL Server, e eu não tenho certeza de como ir sobre implimenting a mesma lógica para atualizações. Também pode haver a complexidade dos registros dentro INSERTED conflitantes entre si.
O segundo, aparentemente melhor, a abordagem seria criar uma restrição que chama uma função definida pelo usuário, passando os PatrolID, GuardID, começa e termina. A função, então, fazer uma ONDE EXISTE consulta de verificação para todos os registros que se sobrepõem as / inicia / termina parâmetros GuardID que não são o registro PatrolID originais. No entanto, eu não tenho certeza do que potenciais efeitos colaterais esta abordagem possa ter.
É a segunda melhor abordagem? Alguém vê as armadilhas, como quando inserir / atualizar várias linhas de uma vez (aqui eu estou preocupado porque linhas dentro desse grupo poderia entrar em conflito, ou seja, a ordem em que são "inserido" faz a diferença). Existe uma melhor maneira de fazer isso (como algum truque fantasia INDEX?)
Solução
Use um depois gatilho para verificar se a restrição de sobreposição não foi violada:
create trigger Patrol_NoOverlap_AIU on Patrol for insert, update as
begin
if exists (select *
from inserted i
inner join Patrol p
on i.GuardId = p.GuardId
and i.PatrolId <> p.PatrolId
where (i.Starts between p.starts and p.Ends)
or (i.Ends between p.Starts and p.Ends))
rollback transaction
end
NOTA: reverter uma transação dentro de um gatilho irá terminar o lote. Ao contrário de uma violação contraint normal, você não será capaz de capturar o erro.
Você pode querer um diferente onde cláusula dependendo de como você define o intervalo de tempo e se sobrepõem. Por exemplo, se você quer ser capaz de dizer Guarda # 1 está em X 6:00 - 07:00 então Y 7:00 para 8:00 o acima não permitiria. Você gostaria em vez disso:
create trigger Patrol_NoOverlap_AIU on Patrol for insert, update as
begin
if exists (select *
from inserted i
inner join Patrol p
on i.GuardId = p.GuardId
and i.PatrolId <> p.PatrolId
where (p.Starts <= i.Starts and i.Starts < p.Ends)
or (p.Starts <= i.Ends and i.Ends < p.Ends))
rollback transaction
end
Onde Starts é o momento os que guardam começa e termina é o momento infinitesimal depois guardando extremidades.
Outras dicas
A maneira mais simples seria usar um procedimento armazenado para as inserções. O procedimento armazenado pode fazer a inserção em uma única instrução:
insert into YourTable
(GuardID, Starts, Ends)
select @GuardID, @Starts, @Ends
where not exists (
select *
from YourTable
where GuardID = @GuardID
and Starts <= @Ends
and Ends >= @Start
)
if @@rowcount <> 1
return -1 -- Failure
Em minha experiência, gatilhos e restrições com UDF de tendem a se tornar muito complexo. Eles têm efeitos colaterais que podem exigir um monte de depuração para descobrir.
Os procedimentos armazenados apenas trabalho, e eles têm a vantagem adicional de que você pode negar permissões INSERT para clientes, dando-lhe o controle refinado sobre o que entra no seu banco de dados.
CREATE TRIGGER [dbo].[emaill] ON [dbo].[email]
FOR INSERT
AS
BEGIN
declare @email CHAR(50);
SELECT @email=i.email from inserted i;
IF @email NOT LIKE '%_@%_.__%'
BEGIN
print 'Triggered Fired';
Print 'Invalid Emaill....';
ROLLBACK TRANSACTION
END
END
Pode ser feito com restrições demasiado: