¡Hola, chicos!
En este artículo, quiero explicarles exactamente qué significa el mensaje "La cadena o los datos binarios se truncarían", cómo podemos identificar qué cadena está causando el error, cómo ocultar este mensaje de error (si lo desea), qué ha impactado el cambio en sys.messages en este tema desde SQL Server 2016+ ¡y mucho más!

Entonces, si tiene dificultades para identificar y corregir las apariciones de este mensaje de error, hoy será la última vez que esto seguirá siendo un problema para usted.

¿Qué es "los datos de cadena o binarios se truncarían"?

Uno de los errores más comunes de SQL Server, el mensaje "La cadena o los datos binarios se truncarían" ocurre cuando un valor intenta insertarse o actualizarse en una tabla y es mayor que el tamaño máximo de campo.

Ejemplo 1: tamaño máximo de campo de 10 caracteres, cadena de 10:

Ejemplo 2: tamaño máximo de campo de 10 caracteres, cadena de 11:

Ejemplo 3: Difícil identificar el error

Mira el ejemplo 3. Ya no estoy insertando un valor fijo, sino de otra tabla. Este tipo de situación puede parecer simple en un escenario como el de los ejemplos 1 y 2, pero cuando inserta varios registros, especialmente tomando datos de varias columnas, es difícil identificar qué registro y qué columna está causando este mensaje de error y esta tarea puede terminar tomando más tiempo del que le gustaría.

Cómo evitar el truncamiento de cadenas

Si desea ignorar el truncamiento de cadenas en un momento u operación determinada, tiene la opción de hacerlo en SQL Server, como ya se demostró en el artículo. SQL Server: ¿por qué NO utilizar SET ANSI_WARNINGS OFF?. No recomiendo usar esta técnica de ninguna manera, ya que es una solución que solo es enmascarar el problema y no corregirlo, pero me gustaría demostrar que existe y que es posible hacerlo.

Cuando utiliza el comando SET ANSI_WARNINGS OFF, evita que el motor de SQL Server genere este error durante la ejecución, lo que provoca que el texto de 14 caracteres se trunque y se almacene en la columna de 10 caracteres. Los caracteres sobrantes serán silenciosamente descartado, ignorando y enmascarando un problema en el registro de datos en su sistema, sin que nadie lo sepa.

Ejemplo:

SET NOCOUNT ON
SET ANSI_WARNINGS OFF

IF ((@@OPTIONS & 8) > 0) PRINT 'SET ANSI_WARNINGS is ON'

IF (OBJECT_ID('tempdb..#Teste') IS NOT NULL) DROP TABLE #Teste
CREATE TABLE #Teste ( Nome VARCHAR(10) )

INSERT INTO #Teste
VALUES ('Dirceu Resende') -- 14 caracteres

SELECT * FROM #Teste

Regrese con SET ANSI_WARNINGS ON (predeterminado):

Regrese con SET ANSI_WARNINGS OFF:

Los datos de cadena o binarios se truncarían en SQL Server 2019

SQL Server 2019 se lanzó el 4 de noviembre de 2019, durante Microsoft Ignite, y con él, se lanzó oficialmente una amplia gama de nuevas funciones.

Una de estas nuevas características son los nuevos mensajes disponibles en sys.messages, que ya les había compartido en mi artículo. SQL Server 2019: lista de nuevas funciones y características desde finales de agosto de 2018:

Con este cambio, ahora es mucho más fácil identificar exactamente dónde se está produciendo el truncamiento de valores:

De acuerdo a documentación de trazas de banderas, traceflag 460 reemplaza el ID de mensaje de truncamiento de datos 8152 por el ID de mensaje 2628. En el nivel de compatibilidad de la base de datos 150 en adelante (2019), el ID de mensaje 2628 es el predeterminado y el traceflag 460 no tiene ningún efecto.

Es decir, este nuevo comportamiento solo funcionará automáticamente en SQL Server 2019, si la base de datos de conexión está en modo de compatibilidad 150 en adelante.

Observación: Este cambio en sys.messages de SQL Server 2019 comenzó en la versión CTP2.0 y hasta que se lanzó la versión final no se activó automáticamente, requiriendo los mismos cambios que en las versiones 2017 y 2016 para funcionar.

Qué cambió a partir de SQL Server 2016+

Con los cambios necesarios para esta implementación en SQL Server 2019, Microsoft terminó lanzando este nuevo mensaje también en las versiones 2016 (comenzando con SP2 Cumulative Update 6) y 2017 (comenzando con Cumulative Update 12). Y para poder utilizar este nuevo mensaje, podemos utilizar 2 formas diferentes:

Forma 1: uso del parámetro de inicialización -T460

La primera y más práctica forma es habilitar traceflag 460 en toda la instancia usando el parámetro de inicialización -T460 en el servicio SQL Server:

Una vez agregado será necesario reiniciar el servicio y a partir de ese momento el nuevo mensaje estará funcionando por defecto, sin necesidad de realizar ningún cambio en la consulta:

Forma 2: usando traceflag 460

Una forma que no requiere reiniciar el servicio SQL Server y tampoco requiere cambiar el código es usando el comando DBCC TRACEON, que le permite activar este traceflag a nivel de sesión y a nivel de instancia (global):

Nivel de sesión (solo afecta a tu sesión):

Nivel de instancia (global – afecta a todas las sesiones):

Guión utilizado:

PRINT @@VERSION

CREATE TABLE dbo.Teste (
    nome1 VARCHAR(10),
    nome2 VARCHAR(10),
    nome3 VARCHAR(10)
)

-- Modo padrão - Mensagem anterior
INSERT INTO dbo.Teste (nome1, nome2, nome3)
VALUES('123456789', '1234567890', '12345678901')

-- Nível de instância (global - afeta todas as sessões)
DBCC TRACEON(460, -1);
GO

-- Nível de sessão (afeta apenas a sua sessão)
DBCC TRACEON(460);
GO

-- Após ativar a traceflag, a mensagem é alterada
INSERT INTO dbo.Teste (nome1, nome2, nome3)
VALUES('123456789', '1234567890', '12345678901')
Notas: Para desactivar el traceflag, utilice el comando DBCC TRACEOFF(460) o DBCC TRACEOFF(460, -1), según el parámetro utilizado en la activación. Si ha habilitado traceflag a nivel de instancia, lo que afecta a todas las sesiones, cuando reinicie el servicio SQL Server, deberá habilitar traceflag nuevamente.
Hubo un error en SQL Server 2017 CU12 y CU13, que provocaba el error “Se truncarían cadenas o datos binarios” en operaciones de inserción de variables tipo tabla, cuando el tamaño de la cadena era mayor que el campo, incluso en los casos en los que el código no se ejecutaba (Ej: Dentro de un IF con condición 1=0). Esto se solucionó en SQL Server 2017 CU14.

Identificar el truncamiento de cadenas antes de SQL Server 2016

Si está utilizando una versión anterior a 2016 y tiene dificultades para identificar qué valores exceden el límite de una columna, le compartiré un script simple que puede identificar este tipo de error incluso en versiones anteriores de SQL Server, creando una tabla “clonada” de la original, con el tamaño de las columnas establecido al máximo, insertando los datos en esta nueva tabla y comparando el tamaño utilizado con el tamaño de las columnas de la tabla original.

Ejemplo de una situación en la que se puede utilizar el script:

Intenté ejecutar mi comando INSERT y me dio un error en algún valor, en alguna columna, que no sé cuál es. Ahora es el momento de pensar en el trabajo que llevará identificar esto, especialmente si tienes alrededor de 100 columnas en lugar de solo estas 3 en el ejemplo... Tener que insertar los datos en una tabla "clon" y consultar y comparar el tamaño más grande de cada columna... Parece laborioso...

Para facilitar el trabajo de identificación de esta columna, compartiré con usted el siguiente script, que realiza esta identificación por usted. Recuerde cambiar el nombre de la tabla fuente y el script INSERT/UPDATE en la tabla clonada con su script original.

DECLARE @Nome_Tabela VARCHAR(255) = 'Teste'

-- Crio uma tabela com a mesma estrutura
IF (OBJECT_ID('tempdb..#Tabela_Clonada') IS NOT NULL) DROP TABLE #Tabela_Clonada
SELECT *
INTO
    #Tabela_Clonada
FROM
    dbo.Teste -- Tabela original (LEMBRE-SE DE ALTERAR AQUI TAMBÉM)
WHERE 
    1=2 -- Não quero copiar os dados

    
-- Agora, vou alterar todas as colunas varchar/char/nvarchar/nchar para varchar(max)
DECLARE @Query VARCHAR(MAX) = ''

SELECT 
    @Query += 'ALTER TABLE #Tabela_Clonada ALTER COLUMN [' + B.[name] + '] ' + UPPER(C.[name]) + '(MAX); '
FROM
    sys.tables A
    JOIN sys.columns B ON B.[object_id] = A.[object_id]
    JOIN sys.types C ON B.system_type_id = C.system_type_id
WHERE
    A.[name] = @Nome_Tabela
    AND B.system_type_id IN (167, 175, 231, 239) -- varchar/char/nvarchar/nchar
    AND B.max_length > 0


EXEC(@Query)


------------------------------------------------------------------------------------------------------------------
-- Faço a inserção dos dados que estão apresentando erro na tabela temporária (LEMBRE-SE DE ALTERAR AQUI TAMBÉM)
------------------------------------------------------------------------------------------------------------------

INSERT INTO #Tabela_Clonada
-- Alterar esse SELECT aqui pelo seu
SELECT [name], [type_desc] + ' | ' + lock_escalation_desc, SCHEMA_NAME([schema_id])
FROM sys.tables


-- Crio uma tabela temporária com o tamanho máximo de caracteres de cada coluna
SET @Query = ''

SELECT
    @Query = @Query + 'SELECT ' + QUOTENAME(A.[name], '''') + ' AS coluna, ' + QUOTENAME(B.[name], '''') + ' AS tipo, MAX(DATALENGTH(' + QUOTENAME(A.[name]) + ')) AS tamanho FROM #Tabela_Clonada UNION '
FROM
    tempdb.sys.columns A
    JOIN sys.types B on B.system_type_id = A.system_type_id and B.[name] <> 'sysname'
WHERE
    A.[object_id] = OBJECT_ID('tempdb..#Tabela_Clonada')
    AND B.system_type_id IN (167, 175, 231, 239) -- varchar/char/nvarchar/nchar

    
SET @Query = LEFT(@Query, LEN(@Query)-6)

IF (OBJECT_ID('tempdb..#Tamanho_Maximo_Coluna') IS NOT NULL) DROP TABLE #Tamanho_Maximo_Coluna
CREATE TABLE #Tamanho_Maximo_Coluna (
    coluna VARCHAR(255),
    tipo VARCHAR(255),
    tamanho INT
)

INSERT INTO #Tamanho_Maximo_Coluna
EXEC(@Query)


-- E por fim, faço a comparação dos dados de uma tabela pela outra
SELECT
    B.[name] AS coluna,
    C.[name] AS tipo,
    B.max_length AS tamanho_atual,
    D.tamanho AS tamanho_necessario
FROM
    sys.tables A
    JOIN sys.columns B ON B.[object_id] = A.[object_id]
    JOIN sys.types C ON B.system_type_id = C.system_type_id
    JOIN #Tamanho_Maximo_Coluna D ON B.[name] = D.coluna
WHERE
    A.[name] = @Nome_Tabela
    AND B.system_type_id IN (167, 175, 231, 239) -- varchar/char/nvarchar/nchar
    AND B.max_length < D.tamanho

Al ejecutar este script, tendremos información exacta sobre qué columnas son demasiado pequeñas para los datos que están recibiendo y cuál es el tamaño ideal para poder almacenar estos datos.

Observación: Tenga cuidado al utilizar este script en tablas muy grandes. Como necesita tomar el tamaño más grande de cada columna de la tabla, esta operación puede resultar un poco lenta y engorrosa en tablas con millones de registros. Entonces, tengo muy cuidadoso!

Espero que hayas disfrutado de este artículo, un fuerte abrazo y ¡nos vemos en el próximo post!
Abrazos

Referencias: