Limites SQL NVARCHAR e VARCHAR

100

Tudo, eu tenho uma grande consulta SQL dinâmica (inevitável). Devido ao número de campos nos critérios de seleção, a string que contém o SQL dinâmico está crescendo mais de 4.000 caracteres. Agora, eu entendo que há um máximo de 4000 definido para NVARCHAR(MAX), mas olhando para o SQL executado no Server Profiler para a instrução

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

Parece funcionar (!?), para outra consulta que também é grande ela lança um erro que está associado a esse limite de 4000 (!?), basicamente corta todo o SQL após esse limite de 4000 e me deixa com um erro de sintaxe. Apesar disso, no criador de perfil, ele está mostrando essa consulta SQL dinâmica por completo (!?).

O que exatamente está acontecendo aqui e devo apenas converter esta variável @SQL para VARCHAR e continuar com isso?

Obrigado pelo seu tempo.

Ps. Também seria bom poder imprimir mais de 4.000 caracteres para olhar essas grandes consultas. Os seguintes são limitados a 4000

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

existe alguma outra maneira legal?

Cavaleiro da Lua
fonte
3
MAX não é sinônimo de limite de 4000, é 1..4000 ou MAX
Alex K.
Por que você marcou a pergunta com C # dll e configuração quando esta é apenas uma pergunta do Sql Server
HatSoft
Editado. Obrigado por detectar ...
MoonKnight
PRINT irá concatenar em 4000 caracteres (para unicode) ou 8000 caracteres (para codificações de byte único). Suspeito que essa seja a fonte da confusão aqui.
redcalx

Respostas:

235

Eu entendo que há um máximo de 4000 definido para NVARCHAR(MAX)

Seu entendimento está errado. nvarchar(max)pode armazenar até (e às vezes além) 2 GB de dados (1 bilhão de caracteres de byte duplo).

De nchar e nvarchar em Livros online, a gramática é

nvarchar [ ( n | max ) ]

O |personagem significa que essas são alternativas. ou seja, você especifica um n ou o literal max.

Se você optar por especificar um específico n, ele deve estar entre 1 e 4.000, mas o uso o maxdefine como um tipo de dados de objeto grande (substituição para o ntextqual está obsoleto).

Na verdade, no SQL Server 2008, parece que para uma variável o limite de 2 GB pode ser excedido indefinidamente, sujeito a espaço suficiente em tempdb( mostrado aqui )

Em relação às outras partes da sua pergunta

O truncamento durante a concatenação depende do tipo de dados.

  1. varchar(n) + varchar(n) irá truncar em 8.000 caracteres.
  2. nvarchar(n) + nvarchar(n) irá truncar em 4.000 caracteres.
  3. varchar(n) + nvarchar(n)irá truncar em 4.000 caracteres. nvarchartem maior precedência, então o resultado énvarchar(4,000)
  4. [n]varchar(max)+ [n]varchar(max)não truncará (para <2 GB).
  5. varchar(max)+ varchar(n)não truncará (para <2 GB) e o resultado será digitado como varchar(max).
  6. varchar(max)+ nvarchar(n)não truncará (para <2 GB) e o resultado será digitado como nvarchar(max).
  7. nvarchar(max)+ varchar(n)primeiro converterá a varchar(n)entrada para nvarchar(n)e, em seguida, fará a concatenação. Se o comprimento da varchar(n)string for maior que 4.000 caracteres, a conversão será para nvarchar(4000)e ocorrerá truncamento .

Tipos de dados de literais de string

Se você usar o Nprefixo e a string tiver <= 4.000 caracteres, ela será digitada como nvarchar(n)onde né o comprimento da string. Portanto N'Foo', será tratado como nvarchar(3)exemplo. Se a string tiver mais de 4.000 caracteres, ela será tratada comonvarchar(max)

Se você não usar o Nprefixo e a string tiver <= 8.000 caracteres, ela será digitada como varchar(n)onde né o comprimento da string. Se mais comovarchar(max)

Para ambos os itens acima, se o comprimento da string for zero, nserá definido como 1.

Elementos de sintaxe mais recentes.

1. A CONCATfunção não ajuda aqui

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

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

O código acima retorna 8000 para ambos os métodos de concatenação.

2. Tenha cuidado com+=

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);`

Devoluções

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

Observe que @Aencontrou truncamento.

Como resolver o problema que você está enfrentando.

Você está obtendo truncamento porque está concatenando dois maxtipos que não são de dados ou porque está concatenando uma varchar(4001 - 8000)string com uma nvarcharstring digitada (par nvarchar(max)).

Para evitar o segundo problema, simplesmente certifique-se de que todos os literais de string (ou pelo menos aqueles com comprimentos no intervalo 4001 - 8000) sejam precedidos por N.

Para evitar o primeiro problema, altere a atribuição de

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

Para

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

de modo que um NVARCHAR(MAX)esteja envolvido na concatenação desde o início (como o resultado de cada concatenação também será, NVARCHAR(MAX)isso se propagará)

Evitando truncamento ao visualizar

Certifique-se de ter selecionado o modo "resultados para grade", então você pode usar

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

As opções de SSMS permitem que você defina uma duração ilimitada para os XMLresultados. O processing-instructionbit evita problemas com personagens como <aparecer como &lt;.

Martin Smith
fonte
2
@Killercam - Você pode receber um elenco implícito ao nvarchar(4000)longo do caminho. Se uma string literal tiver menos de 4.000 caracteres, ela será tratada como nvarchar(x). Concatenar com outro nvarchar(x)valor truncará em vez de aumentar paranvarchar(max)
Martin Smith
2
@Killercam - Você provavelmente está obtendo truncamento de acordo com meu primeiro comentário. Tente alterar a atribuição para de DECLARE @SQL NVARCHAR(MAX) = ''; SET @SQL = @SQL + modo que um NVARCHAR(MAX)esteja envolvido na concatenação.
Martin Smith
2
@Killercam - Provavelmente você tem uma string entre 4.000 e 8.000 caracteres. Com o Nprefixo que será tratado como nvarchar(max)sem ele, ele será tratado como, em varchar(n)seguida, implicitamente convertido nvarchar(4000)quando você concatenar para umnvarchar
Martin Smith
3
estou esclarecido com esta resposta
Mudassir Hasan
1
Resposta incrível. Muito obrigado!
John Bell
6

Ok, então se mais tarde na linha o problema for que você tem uma consulta que é maior do que o tamanho permitido (o que pode acontecer se continuar crescendo), você terá que dividi-la em pedaços e executar os valores da string. Então, digamos que você tenha um procedimento armazenado como o seguinte:

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
fonte
2

Você deve usar texto nvarchar também. isso significa que você simplesmente tem que ter um "N" antes de sua corda enorme e é isso! nenhuma limitação mais

DELARE @SQL NVARCHAR(MAX);
SET @SQL = N'SomeMassiveString > 4000 chars...';
EXEC(@SQL);
GO
Max
fonte
3
Esta não é a imagem inteira ... Se você usar o prefixo N e a string tiver <= 4.000 caracteres, ela será digitada como nvarchar(n)onde n é o comprimento da string. Portanto, N'Foo 'será tratado como nvarchar(3)exemplo. Se a string tiver mais de 4.000 caracteres, ela será tratada como nvarchar(max). Se você não usar o prefixo N e a string tiver <= 8.000 caracteres, ela será digitada como varchar(n)onde n é o comprimento da string. Se mais como varchar(max). Para ambos os
itens
1

A resposta aceita me ajudou, mas eu tropecei ao fazer a concatenação de varchars envolvendo declarações de caso. Eu sei que a pergunta do OP não envolve instruções de caso, mas achei que seria útil postar aqui para outras pessoas como eu, que acabaram aqui enquanto lutavam para construir longas instruções SQL dinâmicas envolvendo instruções de caso.

Ao usar declarações de caso com concatenação de string, as regras mencionadas na resposta aceita se aplicam a cada seção da declaração de caso independentemente.

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
fonte
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
fonte