Conheça também: Onmasters . Ofertas . Divulgue! . Vai.la . Geraboleto . Baixa.la . Assista.la . Joga.la
» Início » Desenvolvimento » Banco de dados e SQL » SQL Server: Geração de seqüenciais de forma automática -->
 
Avaliação: | Publicado em: 20/09/2007
SQL Server: Geração de seqüenciais de forma automática


Divergências

Para mostrar possíveis divergências entre as funções para recuperar o Identity, será utilizada uma situação fictícia de um sistema contábil. Esse sistema possui duas tabelas. A primeira tabela é a que possui os lançamentos contábeis e a segunda tabela é uma auditoria de algumas operações realizadas sobre os lançamentos. Toda vez que um lançamento é inserido ou excluído, um registro é gravado automaticamente (através de uma trigger) na tabela de auditoria e ambas as tabelas possuem um campo Identity. Certamente uma auditoria efetiva não iria usar mecanismos tão simples, mas a idéia é apenas exemplificar algumas divergências e não construir um poderoso sistema de auditoria ou exemplificar como as triggers funcionam.

-- Criação das tabelas
CREATE TABLE tblLancamentoContabil (
    LancamentoID INT IDENTITY,
    Data SMALLDATETIME,
    Valor MONEY,
    Tipo CHAR(1))

CREATE TABLE tblAuditoriaContabil (
    AuditoriaID INT IDENTITY,
    Data SMALLDATETIME,
    LancamentoID INT,
    Operacao CHAR(1))

-- Cria uma Trigger para gravação automática de auditoria
CREATE TRIGGER trgLancamentoContabil
ON tblLancamentoContabil
FOR INSERT, DELETE
AS
BEGIN

    -- Grava o registro inserido se houver
    INSERT INTO tblAuditoriaContabil (Data, LancamentoID, Operacao)
    SELECT GETDATE(), LancamentoID, 'I' FROM INSERTED

    -- Grava o registro deletado se houver
    INSERT INTO tblAuditoriaContabil (Data, LancamentoID, Operacao)
    SELECT GETDATE(), LancamentoID, 'D' FROM DELETED

END

Inicialmente será realizado o cadastro de um lançamento contábil na tabela de lançamentos. Após a realização desse lançamento, automaticamente um registro é gerado na tabela de auditoria informando que um registro foi inserido. Como as duas tabelas tem campos Identity e é o primeiro registro em ambas as tabelas, o ID retornado será igual a um. Nesse primeiro momento, as funções @@identity e Scope_Identity() retornam o mesmo resultado conforme demonstrado abaixo:

-- Faz um novo lançamento
INSERT INTO tblLancamentoContabil (Data, Valor, Tipo)
VALUES (GETDATE(), 200.15, 'C')

-- Captura o valor de @@identity e Scope_Identity()
SELECT
    @@identity As [Identity],
    Scope_Identity() As [Scope_Identity]

-- Verifica os IDs das tabelas
SELECT * FROM tblLancamentoContabil
SELECT * FROM tblAuditoriaContabil

Posteriormente o lançamento (ID 1) é excluído. Essa ação provocará uma entrada na tabela de auditoria que agora possui dois registros (um com ID 1 e outro com ID 2) referentes às operações de INSERT e DELETE. O próximo registro da tabela de lançamentos deve ter ID igual a 2 (o ID foi eliminado e não é reaproveitado) e o próximo registro na tabela de auditoria deve ter ID igual a 3. O script abaixo mostra essas ações:

-- Efetua um DELETE do lancamento
DELETE FROM tblLancamentoContabil WHERE LancamentoID = 1

-- Verifica os IDs das tabelas
SELECT * FROM tblLancamentoContabil
SELECT * FROM tblAuditoriaContabil

-- Faz um novo lançamento
INSERT INTO tblLancamentoContabil (Data, Valor, Tipo)
VALUES (GETDATE(), 200.15, 'D')

Da primeira vez, o Identity era igual a 1, pois ambas as tabelas estavam vazias e o ID era o mesmo. Após a realização do segundo INSERT, como saber de qual tabela o Identity será retornado ? Essa é justamente a diferença entre as funções @@identity e Scope_Identity. O @@identity irá retornar o último Identity gerado pela conexão que no caso refere-se a tabela de auditoria (ID = 3). O Scope_Identity() irá retornar o último Identity gerado pela conexão, mas no caso é considerado o escopo. O escopo que disparou a ação foi o INSERT na tabela de lançamentos, o ID considerado por essa função refere-se a essa tabela. Isso pode ser verificado através dos comandos abaixo:

-- Captura o valor de @@identity e Scope_Identity()
SELECT
    @@identity As [Identity],
    Scope_Identity() As [Scope_Identity]

-- Verifica os IDs das tabelas
SELECT * FROM tblLancamentoContabil
SELECT * FROM tblAuditoriaContabil

Na grande maioria das situações, o escopo é único e nesse caso as funções @@identity e Scope_Identity() trazem resultados idênticos. No entanto, se mais de um escopo estiver envolvido, é preciso saber quando utilizar cada uma para evitar retornar valores errados. Uma possível solução para evitar esse tipo de problema é a utilização da função IDENT_CURRENT. Como essa função recebe o nome da tabela como argumento, ela poderia auxiliar independentemente da quantidade de escopos utilizados.

De fato isso parece ser factível. O problema é que se muitos usuários acessarem o sistema simultaneamente (grande concorrência), pode haver um atraso entre a ação que gera um novo Identity e a recuperação através da função IDENT_CURRENT. As funções @@identity e Scope_Identity() levam em conta o contexto que gerou esses valores e são imunes a problemas de grandes concorrências.