Límites de SQL NVARCHAR y VARCHAR

100

Todo, tengo una consulta SQL dinámica grande (inevitable). Debido a la cantidad de campos en los criterios de selección, la cadena que contiene el SQL dinámico está creciendo a más de 4000 caracteres. Ahora, entiendo que hay un máximo de 4000 establecido para NVARCHAR(MAX), pero mirando el SQL ejecutado en Server Profiler para la declaración

DELARE @SQL NVARCHAR(MAX);
SET @SQL = 'SomeMassiveString > 4000 chars...';
EXEC(@SQL);
GO

Parece funcionar (!?), para otra consulta que también es grande arroja un error que está asociado con este límite 4000 (!?), básicamente recorta todo el SQL después de este límite 4000 y me deja con un error de sintaxis. A pesar de esto en el generador de perfiles, está mostrando esta consulta SQL dinámica en su totalidad (!?).

¿Qué está sucediendo exactamente aquí? ¿Debería convertir esta variable @SQL a VARCHAR y seguir adelante?

Gracias por tu tiempo.

PD. También sería bueno poder imprimir más de 4000 caracteres para ver estas grandes consultas. Los siguientes están limitados a 4000

SELECT CONVERT(XML, @SQL);
PRINT(@SQL);

¿Hay alguna otra forma genial?

Caballero de la Luna
fuente
3
MAX no es sinónimo del límite de 4000, es 1..4000 o MAX
Alex K.
¿Por qué ha etiquetado la pregunta con C # dll y configurando sw cuando esto es solo una pregunta del servidor Sql?
HatSoft
Editado. Gracias por ver ...
MoonKnight
PRINT se concatenará a 4000 caracteres (para Unicode) o 8000 caracteres (para codificaciones de un solo byte). Sospecho que esa es la fuente de la confusión aquí.
redcalx

Respuestas:

235

Entiendo que hay un máximo de 4000 establecido para NVARCHAR(MAX)

Tu comprensión está mal. nvarchar(max)puede almacenar hasta (y a veces más) 2 GB de datos (mil millones de caracteres de doble byte).

De nchar y nvarchar en Libros en línea, la gramática es

nvarchar [ ( n | max ) ]

El |personaje significa que estas son alternativas. es decir, se especifica ya sea n o lo literal max.

Si elige especificar un tipo específico n, debe estar entre 1 y 4.000, pero el uso lo maxdefine como un tipo de datos de objeto grande (el reemplazo ntextestá obsoleto).

De hecho, en SQL Server 2008 parece que para una variable, el límite de 2 GB se puede exceder indefinidamente sujeto a suficiente espacio en tempdb(se muestra aquí )

Respecto a las otras partes de su pregunta

El truncamiento al concatenar depende del tipo de datos.

  1. varchar(n) + varchar(n) se truncará a los 8.000 caracteres.
  2. nvarchar(n) + nvarchar(n) se truncará a 4.000 caracteres.
  3. varchar(n) + nvarchar(n)se truncará a 4.000 caracteres. nvarchartiene mayor precedencia por lo que el resultado esnvarchar(4,000)
  4. [n]varchar(max)+ [n]varchar(max)no se truncará (para <2GB).
  5. varchar(max)+ varchar(n)no se truncará (para <2GB) y el resultado se escribirá como varchar(max).
  6. varchar(max)+ nvarchar(n)no se truncará (para <2GB) y el resultado se escribirá como nvarchar(max).
  7. nvarchar(max)+ varchar(n)primero convertirá la varchar(n)entrada nvarchar(n)ay luego hará la concatenación. Si la longitud de la varchar(n)cadena es superior a 4.000 caracteres, la nvarchar(4000)conversión será ay se producirá el truncamiento .

Tipos de datos de cadenas literales

Si usa el Nprefijo y la cadena tiene <= 4.000 caracteres de longitud, se escribirá nvarchar(n)donde nes la longitud de la cadena. Así N'Foo'será tratado como nvarchar(3)por ejemplo. Si la cadena tiene más de 4000 caracteres, se tratará comonvarchar(max)

Si no usa el Nprefijo y la cadena tiene <= 8,000 caracteres de longitud, se escribirá varchar(n)donde nes la longitud de la cadena. Si más largo comovarchar(max)

Para ambos de los anteriores, si la longitud de la cadena es cero, nse establece en 1.

Elementos de sintaxis más nuevos.

1. La CONCATfunción no ayuda aquí

DECLARE @A5000 VARCHAR(5000) = REPLICATE('A',5000);

SELECT DATALENGTH(@A5000 + @A5000), 
       DATALENGTH(CONCAT(@A5000,@A5000));

Lo anterior devuelve 8000 para ambos métodos de concatenación.

2. Tenga cuidado con+=

DECLARE @A VARCHAR(MAX) = '';

SET @A+= REPLICATE('A',5000) + REPLICATE('A',5000)

DECLARE @B VARCHAR(MAX) = '';

SET @B = @B + REPLICATE('A',5000) + REPLICATE('A',5000)


SELECT DATALENGTH(@A), 
       DATALENGTH(@B);`

Devoluciones

-------------------- --------------------
8000                 10000

Tenga en cuenta que @Aencontró truncamiento.

Cómo resolver el problema que está experimentando.

Obtiene truncamiento porque está concatenando dos maxtipos que no son de datos juntos o porque está concatenando una varchar(4001 - 8000)cadena a una nvarcharcadena escrita (par nvarchar(max)).

Para evitar el segundo problema, simplemente asegúrese de que todos los literales de cadena (o al menos aquellos con longitudes en el rango 4001 - 8000) estén precedidos por N.

Para evitar el primer problema, cambie la asignación de

DECLARE @SQL NVARCHAR(MAX);
SET @SQL = 'Foo' + 'Bar' + ...;

A

DECLARE @SQL NVARCHAR(MAX) = ''; 
SET @SQL = @SQL + N'Foo' + N'Bar'

de modo que an NVARCHAR(MAX)esté involucrado en la concatenación desde el principio (como resultado de cada concatenación también será, NVARCHAR(MAX)esto se propagará)

Evitar el truncamiento durante la visualización

Asegúrese de tener seleccionado el modo "resultados a la cuadrícula", luego puede usar

select @SQL as [processing-instruction(x)] FOR XML PATH 

Las opciones de SSMS le permiten establecer una duración ilimitada para los XMLresultados. El processing-instructionbit evita problemas con personajes como <aparecer como &lt;.

Martin Smith
fuente
2
@Killercam: es posible que obtengas un elenco implícito en nvarchar(4000)el camino. Si un literal de cadena tiene menos de 4000 caracteres, se trata como nvarchar(x). La concatenación de otro nvarchar(x)valor se truncará en lugar de ascender anvarchar(max)
Martin Smith
2
@Killercam - Probablemente estés truncando según mi primer comentario. Intente cambiar la asignación a DECLARE @SQL NVARCHAR(MAX) = ''; SET @SQL = @SQL + para que an NVARCHAR(MAX)esté involucrado en la concatenación.
Martin Smith
2
@Killercam: probablemente tenga una cadena de entre 4.000 y 8.000 caracteres. Con el Nprefijo que se tratará como nvarchar(max)sin él, se tratará como si varchar(n)se lanzara implícitamente a nvarchar(4000)cuando se concatene a unnvarchar
Martin Smith
3
Me ilumina esta respuesta
Mudassir Hasan
1
Respuesta impresionante. ¡Muchas gracias!
John Bell
6

Bien, si más adelante en la línea el problema es que tiene una consulta que es mayor que el tamaño permitido (lo que puede suceder si sigue creciendo), tendrá que dividirla en trozos y ejecutar los valores de cadena. Entonces, digamos que tiene un procedimiento almacenado como el siguiente:

CREATE PROCEDURE ExecuteMyHugeQuery
    @SQL VARCHAR(MAX) -- 2GB size limit as stated by Martin Smith
AS
BEGIN
    -- Now, if the length is greater than some arbitrary value
    -- Let's say 2000 for this example
    -- Let's chunk it
    -- Let's also assume we won't allow anything larger than 8000 total
    DECLARE @len INT
    SELECT @len = LEN(@SQL)

    IF (@len > 8000)
    BEGIN
        RAISERROR ('The query cannot be larger than 8000 characters total.',
                   16,
                   1);
    END

    -- Let's declare our possible chunks
    DECLARE @Chunk1 VARCHAR(2000),
            @Chunk2 VARCHAR(2000),
            @Chunk3 VARCHAR(2000),
            @Chunk4 VARCHAR(2000)

    SELECT @Chunk1 = '',
           @Chunk2 = '',
           @Chunk3 = '',
           @Chunk4 = ''

    IF (@len > 2000)
    BEGIN
        -- Let's set the right chunks
        -- We already know we need two chunks so let's set the first
        SELECT @Chunk1 = SUBSTRING(@SQL, 1, 2000)

        -- Let's see if we need three chunks
        IF (@len > 4000)
        BEGIN
            SELECT @Chunk2 = SUBSTRING(@SQL, 2001, 2000)

            -- Let's see if we need four chunks
            IF (@len > 6000)
            BEGIN
                SELECT @Chunk3 = SUBSTRING(@SQL, 4001, 2000)
                SELECT @Chunk4 = SUBSTRING(@SQL, 6001, (@len - 6001))
            END
              ELSE
            BEGIN
                SELECT @Chunk3 = SUBSTRING(@SQL, 4001, (@len - 4001))
            END
        END
          ELSE
        BEGIN
            SELECT @Chunk2 = SUBSTRING(@SQL, 2001, (@len - 2001))
        END
    END

    -- Alright, now that we've broken it down, let's execute it
    EXEC (@Chunk1 + @Chunk2 + @Chunk3 + @Chunk4)
END
Mike Perrenoud
fuente
2

También debes usar texto nvarchar. eso significa que simplemente tienes que tener una "N" antes de tu cuerda masiva y ¡eso es todo! ya no hay limitación

DELARE @SQL NVARCHAR(MAX);
SET @SQL = N'SomeMassiveString > 4000 chars...';
EXEC(@SQL);
GO
Max
fuente
3
Esta no es la imagen completa ... Si usa el prefijo N y la cadena tiene <= 4,000 caracteres de longitud, se escribirá como nvarchar(n)donde n es la longitud de la cadena. Entonces N'Foo 'será tratado como nvarchar(3)por ejemplo. Si la cadena tiene más de 4.000 caracteres, se tratará como nvarchar(max). Si no usa el prefijo N y la cadena tiene <= 8,000 caracteres, se escribirá como varchar(n)donde n es la longitud de la cadena. Si más largo como varchar(max). Para ambos de los anteriores, si la longitud de la cadena es cero, entonces n se establece en 1.
MoonKnight
1

La respuesta aceptada me ayudó, pero me tropecé mientras hacía la concatenación de varchars que involucraban declaraciones de casos. Sé que la pregunta del OP no implica declaraciones de casos, pero pensé que sería útil publicar esto aquí para otros como yo, que terminaron aquí mientras luchaban por construir declaraciones SQL dinámicas largas que involucren declaraciones de casos.

Cuando se utilizan declaraciones de casos con concatenación de cadenas, las reglas mencionadas en la respuesta aceptada se aplican a cada sección de la declaración de casos de forma independiente.

declare @l_sql varchar(max) = ''

set @l_sql = @l_sql +
case when 1=1 then
    --without this correction the result is truncated
    --CONVERT(VARCHAR(MAX), '')
 +REPLICATE('1', 8000)
 +REPLICATE('1', 8000)
end

print len(@l_sql)
Joe
fuente
0
declare @p varbinary(max)
set @p = 0x
declare @local table (col text)

SELECT   @p = @p + 0x3B + CONVERT(varbinary(100), Email)
 FROM tbCarsList
 where email <> ''
 group by email
 order by email

 set @p = substring(@p, 2, 100000)

 insert @local values(cast(@p as varchar(max)))
 select DATALENGTH(col) as collen, col from @local

result collen > 8000, length col value is more than 8000 chars
Heta77
fuente