Olá, Pessoal!
Tudo bem ?

Hoje vou falar sobre um problema que encontrei recentemente, onde uma rotina utilizava a view de catálogo sys.syscolumns para obter informações das colunas de uma tabela Fato (BI) e a partir de um determinado momento, ao tentar executar a query SELECT * FROM sys.syscolumns, o SQL Server retornava a seguinte mensagem:

Msg 220, Level 16, State 1, Line 1
Arithmetic overflow error for data type smallint, value = 40003.

Essa era a primeira vez que eu via uma simples consulta numa view do catálogo do banco retornar um erro.. Então vamos analisar o nosso cenário:

    1) A mensagem de erro nos diz que houve um estouro num campo smallint, cujo valor seria 40003 (limite do smallint: 32767)
    2) Como é uma view do catálogo, não existe opção de alterá-la para aumentar o tamanho da coluna
    3) As colunas da view sys.syscolumns que possuem o tipo smallint são: xusertype, length, colid, xoffset, colstat, number, colorder, offset, usertype, prec
    4) Analisando as colunas smallint, podemos observar que as únicas colunas onde podemos influenciar nos valores, são as colunas colid e colorder, que são identificadores da coluna dentro da tabela (objeto pai) e identificador da ordem da coluna (geralmente as duas colunas tem o mesmo valor), que podem variar de acordo com a quantidade de colunas da nossa tabela

Suspeitando dessas colunas e tendo em mente que elas são preenchidas com auto-incremento, imaginei que por algum motivo, essa rotina de BI poderia estar apagando e recriando colunas na tabela Fato diariamente, fazendo com que esse número só aumentasse e em um determinado momento, atingisse o limite do tipo de dado smallint. Verifiquei com o analista que era responsável pela rotina que apresentava essa falha e ele me confirmou a suspeita. Realmente a rotina apaga e criava colunas diariamente.

Após confirmar o problema de origem, partimos para a solução por ora, que foi criar uma outra tabela com a mesma estrutura da tabela original, copiar os dados para essa outra tabela, apagar a tabela original e renomear a nova tabela para o nome original. Desta forma, as colunas colid e colorder desta tabela seria “resetados” para 1, 2, 3… A solução final deve ser feita na rotina, mas pelo menos, conseguimos colocar a rotina para funcionar e não deixamos que outras rotinas/usuários fiquem impossibilitados de utilizar essa view de catálogo do banco por causa de uma tabela.

Uma solução rápida e que também poderia resolver o problema por ora, seria alterar a rotina de ETL para utilizar uma das opções abaixo, que possuem a coluna de definição da ordem do tipo int, cujo limite é 2.147.483.647:

  • sys.all_columns (column_id)
  • sys.columns (column_id)
  • INFORMATION_SCHEMA.COLUMNS (ORDINAL_POSITION)

Caso você queira simular esse caso, segue script abaixo:

-- Consultando a view
SELECT * FROM sys.syscolumns


-- Criando a estrutura da tabela
IF (OBJECT_ID('Testes.dbo.Estouro_Sys_Columns') IS NOT NULL) DROP TABLE Testes.dbo.Estouro_Sys_Columns
CREATE TABLE Testes.dbo.Estouro_Sys_Columns (
    Id INT IDENTITY(1,1),
    Nome VARCHAR(50),
    Tmp_Coluna1 VARCHAR(20),
    Tmp_Coluna2 VARCHAR(20),
    Tmp_Coluna3 VARCHAR(20),
    Tmp_Coluna4 VARCHAR(20)
)

CREATE CLUSTERED INDEX SK01_Id ON dbo.Estouro_Sys_Columns(Id)



-- Simulando a rotina sendo executada várias vezes

DECLARE @Contador INT = 1, @Total INT = 10000

WHILE(@Contador <= @Total)
BEGIN
    
    ALTER TABLE Testes.dbo.Estouro_Sys_Columns DROP COLUMN
        Tmp_Coluna1,
        Tmp_Coluna2,
        Tmp_Coluna3,
        Tmp_Coluna4
        
        
    ALTER TABLE Testes.dbo.Estouro_Sys_Columns ADD
        Tmp_Coluna1 VARCHAR(20),
        Tmp_Coluna2 VARCHAR(20),
        Tmp_Coluna3 VARCHAR(20),
        Tmp_Coluna4 VARCHAR(20)
        
    
    SET @Contador = @Contador + 1
    
END


-- Tentando consultar a view novamente
SELECT * FROM sys.syscolumns -- ERRO! Arithmetic overflow error for data type smallint, value = 40003.


-- CORRIGINDO O PROBLEMA - Criação da estrutura igual à tabela original
IF (OBJECT_ID('Testes.dbo.Estouro_Sys_Columns2') IS NOT NULL) DROP TABLE Testes.dbo.Estouro_Sys_Columns2
CREATE TABLE Testes.dbo.Estouro_Sys_Columns2 (
    Id INT IDENTITY(1,1),
    Nome VARCHAR(50),
    Tmp_Coluna1 VARCHAR(20),
    Tmp_Coluna2 VARCHAR(20),
    Tmp_Coluna3 VARCHAR(20),
    Tmp_Coluna4 VARCHAR(20)
)

CREATE CLUSTERED INDEX SK01_Id ON dbo.Estouro_Sys_Columns2(Id)


-- CORRIGINDO O PROBLEMA - Inserindo os dados da tabela (caso haja)
SET IDENTITY_INSERT dbo.Estouro_Sys_Columns2 ON

INSERT INTO dbo.Estouro_Sys_Columns2 ( 
    Id,
    Nome,
    Tmp_Coluna1,
    Tmp_Coluna2,
    Tmp_Coluna3,
    Tmp_Coluna4 
)
SELECT * FROM dbo.Estouro_Sys_Columns


-- CORRIGINDO O PROBLEMA - Apagando a tabela antiga e renomeando a nova
DROP TABLE dbo.Estouro_Sys_Columns
EXEC sp_rename 'dbo.Estouro_Sys_Columns2', 'Estouro_Sys_Columns'


-- Consultando a view
SELECT * FROM sys.syscolumns -- WHERE id = OBJECT_ID('dbo.Estouro_Sys_Columns')