Limites SQL NVARCHAR et VARCHAR

100

Tous, j'ai une grande requête SQL dynamique (inévitable). En raison du nombre de champs dans les critères de sélection, la chaîne contenant le SQL dynamique augmente de plus de 4000 caractères. Maintenant, je comprends qu'il existe un ensemble de 4000 max NVARCHAR(MAX), mais en regardant le SQL exécuté dans Server Profiler pour l'instruction

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

Cela semble fonctionner (!?), pour une autre requête qui est également grande, cela génère une erreur associée à cette limite de 4000 (!?), cela supprime fondamentalement tout le SQL après cette limite de 4000 et me laisse avec une erreur de syntaxe. Malgré cela dans le profileur, il affiche cette requête SQL dynamique dans son intégralité (!?).

Que se passe-t-il exactement ici et devrais-je simplement convertir cette variable @SQL en VARCHAR et continuer?

Merci pour votre temps.

Ps. Ce serait également bien de pouvoir imprimer plus de 4000 caractères pour regarder ces grosses requêtes. Les éléments suivants sont limités à 4000

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

y a-t-il une autre façon cool?

MoonKnight
la source
3
MAX n'est pas synonyme de la limite de 4000, ses 1..4000 ou MAX
Alex K.
Pourquoi avez-vous tagué la question avec C # dll et paramètre alors qu'il ne s'agit que d'une question Sql Server
HatSoft
Édité. Merci d'avoir repéré ...
MoonKnight
PRINT concaténera à 4000 caractères (pour unicode) ou 8000 caractères (pour les encodages à un octet). Je soupçonne que c'est là la source de la confusion.
redcalx

Réponses:

235

Je comprends qu'il existe un ensemble de 4000 max pour NVARCHAR(MAX)

Votre compréhension est fausse. nvarchar(max)peut stocker jusqu'à (et parfois au-delà) 2 Go de données (1 milliard de caractères à deux octets).

De nchar et nvarchar dans les livres en ligne, la grammaire est

nvarchar [ ( n | max ) ]

Le |caractère signifie que ce sont des alternatives. c'est-à-dire que vous spécifiez l' un n ou l' autre ou le littéral max.

Si vous choisissez de spécifier un objet spécifique, ncelui-ci doit être compris entre 1 et 4 000, mais l'utilisation le maxdéfinit comme un type de données objet volumineux (dont le remplacement ntextest déconseillé).

En fait, dans SQL Server 2008, il semble que pour une variable, la limite de 2 Go puisse être dépassée indéfiniment sous réserve d'un espace suffisant dans tempdb( montré ici )

Concernant les autres parties de votre question

La troncature lors de la concaténation dépend du type de données.

  1. varchar(n) + varchar(n) tronquera à 8 000 caractères.
  2. nvarchar(n) + nvarchar(n) tronquera à 4 000 caractères.
  3. varchar(n) + nvarchar(n)tronquera à 4 000 caractères. nvarchara une priorité plus élevée donc le résultat estnvarchar(4,000)
  4. [n]varchar(max)+ [n]varchar(max)ne tronquera pas (pour <2 Go).
  5. varchar(max)+ varchar(n)ne tronquera pas (pour <2 Go) et le résultat sera tapé comme varchar(max).
  6. varchar(max)+ nvarchar(n)ne tronquera pas (pour <2 Go) et le résultat sera tapé comme nvarchar(max).
  7. nvarchar(max)+ varchar(n)convertira d'abord l' varchar(n)entrée en nvarchar(n)puis effectuera la concaténation. Si la longueur de la varchar(n)chaîne est supérieure à 4 000 caractères, la conversion sera effectuée nvarchar(4000)et une troncature se produira .

Types de données de littéraux de chaîne

Si vous utilisez le Npréfixe et que la chaîne est <= 4 000 caractères, il sera tapé comme nvarchar(n)nest la longueur de la chaîne. Ainsi N'Foo'sera traité comme nvarchar(3)par exemple. Si la chaîne contient plus de 4000 caractères, elle sera traitée commenvarchar(max)

Si vous n'utilisez pas le Npréfixe et que la chaîne est <= 8 000 caractères, elle sera tapée comme varchar(n)nest la longueur de la chaîne. Si plus long quevarchar(max)

Pour les deux éléments ci-dessus, si la longueur de la chaîne est égale à zéro, elle nest définie sur 1.

Nouveaux éléments de syntaxe.

1. La CONCATfonction n'aide pas ici

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

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

Ce qui précède renvoie 8000 pour les deux méthodes de concaténation.

2. Soyez prudent avec+=

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

Retour

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

Notez que la @Atroncature rencontrée.

Comment résoudre le problème que vous rencontrez.

Vous obtenez une troncature soit parce que vous concaténez deux maxtypes de données non ensemble, soit parce que vous concaténez une varchar(4001 - 8000)chaîne en une nvarcharchaîne typée (paire nvarchar(max)).

Pour éviter le deuxième problème, assurez-vous simplement que tous les littéraux de chaîne (ou au moins ceux dont la longueur est comprise entre 4001 et 8000) sont précédés de N.

Pour éviter le premier problème, modifiez l'attribution de

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

À

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

de sorte qu'un NVARCHAR(MAX)est impliqué dans la concaténation depuis le début (comme le résultat de chaque concaténation sera également NVARCHAR(MAX)cela se propagera)

Éviter la troncature lors de la visualisation

Assurez-vous que vous avez sélectionné le mode «résultats sur grille», puis utilisez

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

Les options SSMS vous permettent de définir une longueur illimitée pour les XMLrésultats. Le processing-instructionbit évite les problèmes avec les caractères tels que <apparaître comme &lt;.

Martin Smith
la source
2
@Killercam - Vous pourriez obtenir un casting implicite en nvarchar(4000)cours de route. Si un littéral de chaîne contient moins de 4 000 caractères, il est traité comme nvarchar(x). La concaténation avec une autre nvarchar(x)valeur tronquera plutôt que l'upcast versnvarchar(max)
Martin Smith
2
@Killercam - Vous obtenez probablement une troncature selon mon premier commentaire. Essayez de changer l’affectation en DECLARE @SQL NVARCHAR(MAX) = ''; SET @SQL = @SQL + pour qu’un NVARCHAR(MAX)soit impliqué dans la concaténation.
Martin Smith
2
@Killercam - Vous avez probablement une chaîne de 4 000 à 8 000 caractères. Avec le Npréfixe qui sera traité comme nvarchar(max)sans lui, il sera traité comme varchar(n)alors implicitement converti nvarchar(4000)lorsque vous concaténez en unnvarchar
Martin Smith
3
je suis éclairé par cette réponse
Mudassir Hasan
1
Réponse géniale. Merci beaucoup!
John Bell
6

D'accord, donc si plus tard, le problème est que vous avez une requête supérieure à la taille autorisée (ce qui peut arriver si elle continue de croître), vous devrez la diviser en morceaux et exécuter les valeurs de chaîne. Donc, disons que vous avez une procédure stockée comme la suivante:

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
la source
2

Vous devez également utiliser le texte nvarchar. cela signifie que vous devez simplement avoir un "N" avant votre chaîne massive et c'est tout! plus aucune limitation

DELARE @SQL NVARCHAR(MAX);
SET @SQL = N'SomeMassiveString > 4000 chars...';
EXEC(@SQL);
GO
Max
la source
3
Ce n'est pas l'image entière ... Si vous utilisez le préfixe N et que la chaîne est <= 4 000 caractères, elle sera tapée comme nvarchar(n)où n est la longueur de la chaîne. Donc N'Foo 'sera traité comme nvarchar(3)par exemple. Si la chaîne contient plus de 4 000 caractères, elle sera traitée comme nvarchar(max). Si vous n'utilisez pas le préfixe N et que la chaîne est <= 8 000 caractères, elle sera tapée comme varchar(n)où n est la longueur de la chaîne. Si plus long que varchar(max). Pour les deux éléments ci-dessus, si la longueur de la chaîne est égale à zéro, n est défini sur 1.
MoonKnight
1

La réponse acceptée m'a aidé mais j'ai trébuché en faisant la concaténation de varchars impliquant des déclarations de cas. Je sais que la question du PO n'implique pas d'énoncés de cas, mais j'ai pensé que ce serait utile de poster ici pour d'autres comme moi qui se sont retrouvés ici tout en luttant pour créer de longues instructions SQL dynamiques impliquant des instructions de cas.

Lors de l'utilisation d'instructions case avec concaténation de chaînes, les règles mentionnées dans la réponse acceptée s'appliquent à chaque section de l'instruction case indépendamment.

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
la source
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
la source