Limiti SQL NVARCHAR e VARCHAR


100

Tutto, ho una grande query SQL dinamica (inevitabile). A causa del numero di campi nei criteri di selezione, la stringa contenente l'SQL dinamico cresce di oltre 4000 caratteri. Ora, capisco che esiste un massimo di 4000 impostato per NVARCHAR(MAX), ma guardando l'SQL eseguito in Server Profiler per l'istruzione

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

Sembra funzionare (!?), per un'altra query che è anche grande genera un errore che è associato a questo limite di 4000 (!?), sostanzialmente taglia tutto l'SQL dopo questo limite di 4000 e mi lascia con un errore di sintassi. Nonostante ciò nel profiler, mostra questa query SQL dinamica per intero (!?).

Cosa sta succedendo esattamente qui e dovrei semplicemente convertire questa variabile @SQL in VARCHAR e andare avanti?

Grazie per il tuo tempo.

Ps. Sarebbe anche bello poter stampare più di 4000 caratteri per esaminare queste grandi query. I seguenti sono limitati a 4000

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

c'è qualche altro modo interessante?


3
MAX non è sinonimo del limite di 4000, il suo 1..4000 o MAX
Alex K.

Perché hai taggato la domanda con C # dll e impostazione swhen questa è solo una domanda di SQL Server
HatSoft,

Modificato. Grazie per aver notato ...
MoonKnight,

PRINT si concatenerà a 4000 caratteri (per Unicode) o 8000 caratteri (per codifiche a byte singolo). Sospetto che questa sia la fonte della confusione qui.
redcalx

Risposte:


235

Capisco che sia impostato un massimo di 4000 NVARCHAR(MAX)

La tua comprensione è sbagliata. nvarchar(max)può memorizzare fino a (e talvolta oltre) 2 GB di dati (1 miliardo di caratteri a doppio byte).

Da nchar e nvarchar nei libri in linea la grammatica è

nvarchar [ ( n | max ) ]

Il |carattere significa che queste sono alternative. cioè si specifica o n o il letterale max.

Se si sceglie di specificare uno specifico, nquesto deve essere compreso tra 1 e 4.000, ma l'utilizzo lo maxdefinisce come un tipo di dati oggetto di grandi dimensioni (la sostituzione ntextè deprecata).

Infatti in SQL Server 2008 sembra che per una variabile il limite di 2GB possa essere superato indefinitamente previo spazio sufficiente in tempdb( Mostrato qui )

Per quanto riguarda le altre parti della tua domanda

Il troncamento durante la concatenazione dipende dal tipo di dati.

  1. varchar(n) + varchar(n) troncerà a 8.000 caratteri.
  2. nvarchar(n) + nvarchar(n) troncerà a 4.000 caratteri.
  3. varchar(n) + nvarchar(n)troncerà a 4.000 caratteri. nvarcharha una precedenza maggiore, quindi il risultato ènvarchar(4,000)
  4. [n]varchar(max)+ [n]varchar(max)non verrà troncato (per <2 GB).
  5. varchar(max)+ varchar(n)non verrà troncato (per <2 GB) e il risultato verrà digitato come varchar(max).
  6. varchar(max)+ nvarchar(n)non verrà troncato (per <2 GB) e il risultato verrà digitato come nvarchar(max).
  7. nvarchar(max)+ varchar(n)convertirà prima l' varchar(n)input in nvarchar(n)e poi eseguirà la concatenazione. Se la lunghezza della varchar(n)stringa è maggiore di 4.000 caratteri, verrà eseguito il cast e verrà eseguito il nvarchar(4000)troncamento .

Tipi di dati di stringhe letterali

Se si utilizza il Nprefisso e la stringa è <= 4.000 caratteri, verrà digitata come nvarchar(n)dove nè la lunghezza della stringa. Quindi N'Foo'sarà trattato come nvarchar(3)per esempio. Se la stringa è più lunga di 4.000 caratteri, verrà trattata comenvarchar(max)

Se non utilizzi il Nprefisso e la stringa è <= 8.000 caratteri, verrà digitata come varchar(n)dove nè la lunghezza della stringa. Se più a lungovarchar(max)

Per entrambi i precedenti, se la lunghezza della stringa è zero, nviene impostata su 1.

Elementi di sintassi più recenti.

1. La CONCATfunzione non aiuta qui

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

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

Quanto sopra restituisce 8000 per entrambi i metodi di concatenazione.

2. Stai attento 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);`

ritorna

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

Notare che si è @Averificato un troncamento.

Come risolvere il problema riscontrato.

Stai ottenendo il troncamento o perché stai concatenando due maxtipi di dati non insieme o perché stai concatenando una varchar(4001 - 8000)stringa a una nvarcharstringa digitata (pari nvarchar(max)).

Per evitare il secondo problema, assicurati semplicemente che tutte le stringhe letterali (o almeno quelle con lunghezze comprese tra 4001 e 8000) siano precedute da N.

Per evitare il primo problema, modificare l'assegnazione da

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

Per

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

in modo che un NVARCHAR(MAX)sia coinvolto nella concatenazione dall'inizio (poiché il risultato di ogni concatenazione sarà anche NVARCHAR(MAX)questo si propagherà)

Evitare il troncamento durante la visualizzazione

Assicurati di avere selezionato la modalità "risultati su griglia", quindi puoi utilizzarla

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

Le opzioni SSMS consentono di impostare una lunghezza illimitata per i XMLrisultati. Il processing-instructionbit evita problemi con personaggi come <apparire come &lt;.


2
@Killercam - Potresti ricevere un cast implicito nvarchar(4000)lungo la strada. Se una stringa letterale è inferiore a 4.000 caratteri, viene trattata come nvarchar(x). Concatenando ad esso un altro nvarchar(x)valore verrà troncato piuttosto che upcast anvarchar(max)
Martin Smith,

2
@Killercam - Probabilmente stai ricevendo il troncamento secondo il mio primo commento. Prova a cambiare l'assegnazione in in DECLARE @SQL NVARCHAR(MAX) = ''; SET @SQL = @SQL + modo che an NVARCHAR(MAX)sia coinvolto nella concatenazione.
Martin Smith,

2
@Killercam - Probabilmente hai una stringa compresa tra 4.000 e 8.000 caratteri. Con il Nprefisso che verrà trattato come nvarchar(max)senza di esso, verrà considerato come varchar(n)poi cast implicitamente nvarchar(4000)quando ti concateni a unnvarchar
Martin Smith,

3
Sono illuminato da questa risposta
Mudassir Hasan

1
Risposta fantastica. Grazie mille!
John Bell

6

Ok, quindi se in seguito il problema è che hai una query maggiore della dimensione consentita (che può accadere se continua a crescere), dovrai suddividerla in blocchi ed eseguire i valori della stringa. Quindi, diciamo che hai una stored procedure come la seguente:

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

2

Devi usare anche il testo nvarchar. questo significa che devi semplicemente avere una "N" prima della tua stringa enorme e il gioco è fatto! nessuna limitazione più

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

3
Questa non è l'intera immagine ... Se si utilizza il prefisso N e la stringa è <= 4.000 caratteri, verrà digitata come nvarchar(n)dove n è la lunghezza della stringa. Quindi N'Foo 'verrà trattato come nvarchar(3)per esempio. Se la stringa è più lunga di 4.000 caratteri, verrà trattata come nvarchar(max). Se non si utilizza il prefisso N e la stringa è <= 8.000 caratteri, verrà digitata come varchar(n)dove n è la lunghezza della stringa. Se più a lungo varchar(max). Per entrambi i casi precedenti, se la lunghezza della stringa è zero, n è impostato su 1.
MoonKnight

1

La risposta accettata mi ha aiutato, ma sono inciampato durante la concatenazione di varchar che coinvolgono dichiarazioni di casi. So che la domanda dell'OP non riguarda le istruzioni case, ma ho pensato che sarebbe stato utile postare qui per altri come me che sono finiti qui mentre lottavano per costruire lunghe istruzioni SQL dinamiche che coinvolgono istruzioni case.

Quando si utilizzano istruzioni case con concatenazione di stringhe, le regole menzionate nella risposta accettata si applicano a ciascuna sezione dell'istruzione case in modo indipendente.

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)

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.