AUTOINCREMENTO SEM PERDER SEQUENCIA

LUIS.HERRERA 23/04/2013 10:58:19
#422349
Bom dia.
Tenho algumas tabelas, onde o ID tem que ser sequencial e não pode ter lacunas ou ser preenchido posteriormente com novos registros, tem que manter a sequencia correta de inserção e sem falhas na numeração.

Nesse cenário o campo autoincremento não serve. Pesquisando vi que é reprovável usar o MAX + 1, por diversos problemas de bloquear tabelas e impedir até consultas, em acessos concorrentes.

Então encontrei um código que usa Trigger e uma tabela de IDs para fazer isso para todas as tabelas nessa situação. O problema é que não entendi algumas coisas, e como não sei muito se MS SQL, gostaria da ajuda de vocês:

Nota: C# 2008 e SQL 2005 Express.

Código Encontrado:

-- Cria a tabela de pessoas
CREATE TABLE tblPessoas (IDPessoa INT NOT NULL, NomePessoa VARCHAR(50))
ALTER TABLE tblPessoas ADD CONSTRAINT PKPessoa PRIMARY KEY (IDPessoa)

-- Cria a tabela de contadores (IDs)
CREATE TABLE tblContadores (NomeTabela VARCHAR(20), Valor INT)
INSERT INTO tblContadores VALUES ([ô]tblPessoas[ô],0)

-- Cria a trigger de inserção
CREATE TRIGGER tblSeqPessoa ON tblPessoas
INSTEAD OF INSERT
[txt-color=#e80000]AS BEGIN[/txt-color] //Não entendi isso é parte da Trigger ou relacionado a parte de baixo?

-- Captura o ID atual (bloqueando-o para que duas pessoas não peguem o mesmo ID)
DECLARE @IDPessoa INT
SET @IDPessoa = (SELECT VALOR FROM tblContadores (XLOCK) WHERE NomeTabela = [ô]tblPessoas[ô])
SET @IDPessoa = @IDPessoa + 1

-- Atualiza o contador
UPDATE tblContadores SET Valor = Valor + 1 WHERE NomeTabela = [ô]tblPessoas[ô]

-- Insere o registro
INSERT INTO tblPessoas (IDPessoa, NomePessoa)
SELECT @IDPessoa AS IDPessoa, NomePessoa FROM INSERTED
[txt-color=#e80000]END[/txt-color] // Esse end finaliza o que? Onde foi seu início no AS BEGIN ?


// Essa parte ví que são as ações do programa para testar a estrutura das tabelas no banco
-- Simula uma inserção
INSERT INTO tblPessoas (NomePessoa) VALUES ([ô]Nome 1[ô])

-- Verifica o registro
SELECT IDPessoa, NomePessoa FROM tblPessoas

-- Verifica o contador
SELECT NomeTabela, Valor FROM tblContadores



[txt-color=#e80000]Perguntas:[/txt-color]
1- As partes em vermelho não entendi, tem meu comentário ao lado.

2- Ao usar uma transação no C#, farei alterações em várias tabelas simultaneamente, onde haverá a inclusão de um registro na tabela principal (pegando o ID pelo Trigger acima) e inclusão em outras tabelas auxiliares usando esse ID para relacionamento. A dúvida é, se ocorrer um erro onde a transação terá um Rollback, como ficará essa numeração [Ô]INCREMENTADA[Ô] pelo trigger, uma vez que tem um UPDATE nele para o Valor = Valor + 1. O Rollback irá reverter isso também?

KERPLUNK 23/04/2013 11:11:59
#422352
O melhor truque para seu caso é:
Para fazer a inserção no próximo Id disponível, você antes precisa saber se existe uma lacuna. Para isso, você faz assim:
Conte quantos registros tem na tabela. Digamos que tenham sido 10. 10 registros, seriam 1,2,3,4,5,6...10, certo? Então, a soma dos números de um à dez é 55, logo, se essa soma não der 55, então tem alguma lacuna no meio. Para saber qual o que falta, subtraia o valor da soma dos id[ô]s de 55, por exemplo, a soma dos registros deu 49, subtraindo 49 de 55, sobra 6, logo o ID que falta é o 6, então insira o próximo usando o 6. Parace um pouco complicado, mas são apenas duas operações de banco bem leves: A contagem de registros e a soma dos id[ô]s
LUIS.HERRERA 23/04/2013 11:40:59
#422357
Kerplunk não é isso que eu preciso, pois como expliquei não posso usar em alguns caso o AutoIncremento para exatamente não gerar lacunas, uma vez que também não posso prrencher essas lacunas com outros registros. O que quero dizer é que obrigatoriamente a numeração tem que ser sequencial e um registro com data de hoje, não pode ser gravado com numeração anterior ao de ontem para preencher uma lacuna como sugeriu.

No sistema que tenho com MS Access uso o MAX + 1, mas nessa migração para MS SQL Server, isso poderá dar muitos problemas. Por isso achei esse código, mas estou querendo entender algumas coisas nele.

Como perguntei sobre o AS BEGIN e se tem relação com o END mais abaixo e se uma transação irá também reverter o Update to Trigger que incrementa a tabala de índices dessa técnica sugerida no código?
KERPLUNK 23/04/2013 11:58:27
#422359
BEGIN....END tem o mesmo papel das chaves no C# {.....} e não é [Ô]junto[Ô] AS e BEGIN são coisas separadas, entenda mais ou menos assim, traduzindo a procedure para [Ô]português[Ô]:
Crie um gatilho na tabela tblPessoas (Create trigger...)
Ao invés de insert (instead of insert)
AS (execute o seguinte bloco)
BEGIN (inicio do bloco)
...
...
...
END (fim do bloco)

Essa procedure acima, nada mais é que inserção de modo normal, nada que você não faria exatamente igual usando blocos de código e SQL. Mas acho que entendi o que você quer e se entendi, a saída é usar o MAX + 1 mesmo
LUIS.HERRERA 23/04/2013 12:27:47
#422365
Entendi, então achei esse outro que faz isso, mas aqui tem outras dúvidas também:

create table tel_pac
(cod_pac int not null,
tel int not null)
go
alter table tel_pac add constraint pk_codpac primary key(cod_pac)
go
create trigger auto_num
on tel_pac
instead of insert
as
declare @cod int,@tel int
select @cod = case when max(cod_pac) is null then 1 else max(cod_pac)+1 end from tel_pac(xlock)
select @tel=i.tel from inserted i
insert into tel_pac(cod_pac,tel) values (@cod,@tel)


1- a parte do código abaixo está tudo [Ô]junto[Ô] e faz parte do AS ou são coisas separadas?
declare @cod int,@tel int
select @cod = case when max(cod_pac) is null then 1 else max(cod_pac)+1 end from tel_pac(xlock)
select @tel=i.tel from inserted i
insert into tel_pac(cod_pac,tel) values (@cod,@tel)

2- Nessa linha abaixo, o que é o (i) e o inserted ?
select @tel=i.tel from inserted i

3- A última linha faz parte do código que irá gerar o ID ou é um exemplo de uso da inserção?
insert into tel_pac(cod_pac,tel) values (@cod,@tel)

4- Fazendo como está aqui, a mesma dúvida do anterior, se eu cancelar uma transação essa trigger irá anular a geração do último ID? Pergunto porque pelos exemplos, isso é feito pelo Banco e não pelo código C# da aplicação, então não sei qual o resultado nesse caso.
KERPLUNK 23/04/2013 13:55:37
#422379
1 - São coisas diferentes, abaixo do [Ô]as[Ô] está o código a ser executado pela trigger
2 - [Ô]Inserted[Ô] e [Ô]Deleted[Ô] são pseudo-tabelas que retorna o último registro inserido na tabela atual, no caso, o registro que disparou a trigger. [Ô]i[Ô] é apenas um alias para isso.
3 - Ela faz parte da trigger
4 - Se você iniciar uma transação na conexão em que for executar a alteração na tabela que vai disparar a trigger, essa transação será respeitada pela trigger, incluindo para commit e rollback.
NILSONTRES 23/04/2013 16:14:24
#422383
Luis,
A muitos anos utilizo algo quase igual ao Max, Bco Mysql, utilizo uma tabela só para gerenciar os numeros de registros, exemplo das Nfe[ô]s.
Tabela_Add. Nela é colocado o numero da proxima nota, então antes da inserção, é feito uma consulta nessa tabela para saber qual o nº da proxima nota, ai que entra o perigo de sistema em redes, um outro usuario pode pegar o mesmo numero, então a tabela que recebe esse numero tem o campo ID, por exemplo, ele é chave primaria, então quando da inserção, se esse numero já estiver inserido, ira gerar o erro:[Ô]Duplicate entry[Ô]
Então o sistema faz um looping, acrescendo +1. até que o erro acabe.
Ao final da inserção, ai sim se atualiza Tabela_Add=Tabela_Add+1.
Nunca mais tive problemas, e em sistemas de fluxo, medio a alto.

FELLIPEASSIS 23/04/2013 22:23:02
#422406
no caso do identity ele segue uma sequencia exata ao menos que entre no bloco catch da procedure dai ele usa o proximo nº
mais no caso se for deletado algum registro anterior ai sai da sequecia numerica ñ? o proprio SGBD faz isso, se não fizer dai é um GA, e qual seria a funcionalidade?
LAUROFELIPE 23/04/2013 22:40:19
#422410
Pelo pouco que conheço, campo AutoIncrement não deixa lacunas.
Porém, se o sistema permite Exclusão de registros, aí, elas aparecerão.
A menos que seja tão importante que você tenha que ao invés de excluir, ZERAR todas as informações e marcar uma flag como EXCLUIDO. Mas, há coerência?
Qualquer que seja o método para gerar a pk, se há exclusão, haverá brechas.
LUIS.HERRERA 24/04/2013 09:08:31
#422423
Pessoal o problema não é a exclusão, pois nessas tabelas [Ô]Críticas[Ô] não existe exclusão de dados. O problema é que usando autoincremento, se der erro na inserção o MS SQL perde o número, ou seja, ele incrementa mas não salva o registro em função do Rollback gerado pelo erro na hora de gravar. Por isso o autoincremento não serve nestas tabelas específicas.

Nilsontres eu também tenho um sistema assim com VB6 e MSAccess e funciona perfeitamente, pois tudo é feito via código no VB. Agora estou migrando para C# com SQL Server e por isso estou tentando aprender como fazer com a novas linguagens.

Terei ao menos umas 10 tabelas nessa situação que terei de controlar a numeração para não ter a perda do ID na hora do Insert. Li algo sobre no SQL Server ter uma tabela auxiliar só para guardar o nome das tabelas críticas e um ID para elas, ao incluir um registro, verificava nessa tabela o nome da tabela de origem e seu ID para gravar, nesse caso havia um bloqueio da tabela para não ser criado um ID semelhante enquanto todo o processo de gravação não fosse concluido, mas como não conheço SQL tão bem para fazer isso, não entendi exatamente como seria isso.

Você poderia passar esse seu código?
NILSONTRES 24/04/2013 09:35:42
#422425
Citação:

O problema é que usando autoincremento, se der erro na inserção o MS SQL perde o número, ou seja, ele incrementa mas não salva o registro em função do Rollback gerado pelo erro na hora de gravar. Por isso o autoincremento não serve nestas tabelas específicas.


é isso mesmo.

Citação:

Nilsontres eu também tenho um sistema assim com VB6 e MSAccess e funciona perfeitamente, pois tudo é feito via código no VB. Agora estou migrando para C# com SQL Server e por isso estou tentando aprender como fazer com a novas linguagens.


Mas a linguagem que vc utiliza não altera, eu faço isso em MSAccess com vb6 e .net com MYSQL.
Se vc Utiliza Transações, o que eu acho imprescindível para quem quer um sistema seguro. Obs: Incrivel que tem muita gente que nem sabe o que é isso, imagine o risco que corre um sistema sem transações.
Voltando la, com transações, que eu saiba não tem como evitar essa lacuna utilizando Auto Incremento.
Página 1 de 2 [16 registro(s)]
Tópico encerrado , respostas não são mais permitidas