web-dev-qa-db-fra.com

Limites SQL NVARCHAR et VARCHAR

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 code SQL dynamique dépasse 4 000 caractères. Je comprends maintenant qu’il existe un maximum de 4 000 défini pour NVARCHAR(MAX), mais en regardant le SQL exécuté dans Profiler Server pour l’instruction 

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

Cela semble fonctionner (!?). Pour une autre requête également volumineuse, une erreur associée à cette limite de 4000 (!?) est supprimée. Elle supprime tout le SQL après cette limite de 4000 et me laisse avec une erreur de syntaxe. Malgré cela, l'éditeur de profil affiche cette requête SQL dynamique dans full (!?). 

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

Merci pour votre temps.

Ps. Il serait également agréable de pouvoir imprimer plus de 4000 caractères pour examiner ces grandes requêtes. Les suivants sont limités à 4000 

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

y a-t-il une autre manière cool?

92
MoonKnight

Je comprends qu’un maximum de 4000 est défini pour NVARCHAR(MAX)

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

De nchar et nvarchar dans Livres en ligne, la grammaire est 

nvarchar [ ( n | max ) ]

Le caractère | signifie qu'il s'agit d'alternatives. c'est-à-dire que vous spécifiez soitn ou le littéral max.

Si vous choisissez de spécifier une n spécifique, celle-ci doit être comprise entre 1 et 4 000, mais utiliser max le définit comme un type de données d'objet volumineux (en remplacement de ntext qui est obsolète).

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

En ce qui concerne 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. nvarchar a une priorité plus élevée donc le résultat est nvarchar(4,000)
  4. [n]varchar(max) + [n]varchar(max) ne sera pas tronqué (pour <2 Go). 
  5. varchar(max) + varchar(n) ne tronquera pas (pour <2 Go) et le résultat sera saisi sous la forme varchar(max).
  6. varchar(max) + nvarchar(n) ne tronquera pas (pour <2 Go) et le résultat sera saisi sous la forme nvarchar(max)
  7. nvarchar(max) + varchar(n) convertira d'abord l'entrée varchar(n) en nvarchar(n), puis effectuera la concaténation. Si la longueur de la chaîne varchar(n) est supérieure à 4 000 caractères, la conversion sera en nvarchar(4000) et la troncature se produira.

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

Si vous utilisez le préfixe N et que la chaîne a une longueur <= 4 000 caractères, le nom sera nvarchar(n)n correspond à la longueur de la chaîne. Donc, N'Foo' sera traité comme nvarchar(3) par exemple. Si la chaîne compte 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 contient <= 8 000 caractères, il sera saisi sous la forme varchar(n)n correspond à la longueur de la chaîne. Si plus longtemps que varchar(max)

Dans les deux cas précédents, si la longueur de la chaîne est égale à zéro, n est défini sur 1.

Éléments de syntaxe plus récents.

1. La fonction CONCAT 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. Faites attention 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);`

Résultats

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

Notez que @A a rencontré une troncature.

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

Vous obtenez une troncature soit parce que vous concaténez deux types de données non max ensemble, soit parce que vous concaténez une chaîne varchar(4001 - 8000) en une chaîne nvarchar (même nvarchar(max)).

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

Pour éviter le premier problème, changez l’affectation 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) soit impliqué dans la concaténation depuis le début (car le résultat de chaque concaténation sera également NVARCHAR(MAX) cela se propagera)

Éviter la troncature lors de l'affichage

Assurez-vous que le mode "résultats sur la grille" est sélectionné, puis vous pouvez utiliser

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

Les options SSMS vous permettent de définir une longueur illimitée pour les résultats XML. Le bit processing-instruction évite les problèmes avec des caractères tels que < qui apparaît sous la forme &lt;.

218
Martin Smith

OK, donc si plus tard dans la ligne 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 le valeurs de chaîne. Donc, supposons que vous ayez une procédure stockée comme celle-ci:

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
6
Mike Perrenoud

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 de limite

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

La réponse acceptée m'a aidée, mais j'ai été prise en défaut lors de la concaténation de varchars impliquant des déclarations de cas. Je sais que la question du PO ne concerne pas les déclarations de cas, mais j’ai pensé qu’il serait utile de poster ici pour des personnes comme moi qui se sont retrouvées ici tout en luttant pour créer de longues instructions SQL dynamiques impliquant des déclarations 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)
1
Joe
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
0
Heta77