Ограничения SQL NVARCHAR и VARCHAR

100

Все, у меня большой (неизбежный) динамический SQL-запрос. Из-за количества полей в критериях выбора строка, содержащая динамический SQL, превышает 4000 символов. Теперь я понимаю, что существует максимальное значение 4000 NVARCHAR(MAX), но глядя на выполненный SQL в Server Profiler для оператора

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

Кажется, работает (!?), для другого запроса, который также является большим, он выдает ошибку, связанную с этим пределом 4000 (!?), он в основном обрезает весь SQL после этого ограничения 4000 и оставляет меня с синтаксической ошибкой. Несмотря на это в профилировщике, он показывает этот динамический SQL-запрос полностью (!?).

Что именно здесь происходит, и должен ли я просто преобразовать эту переменную @SQL в VARCHAR и продолжить?

Спасибо за ваше время.

Пс. Также было бы неплохо иметь возможность распечатать более 4000 символов, чтобы просмотреть эти большие запросы. Следующие ограничены до 4000

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

есть ли другой крутой способ?

Лунный рыцарь
источник
3
MAX не является синонимом лимита 4000, его 1..4000 или MAX
Alex K.
Почему вы пометили вопрос с помощью C # dll и настройки sw? Тогда это просто вопрос Sql Server
HatSoft
Отредактировано. Спасибо за обнаружение ...
MoonKnight
PRINT объединяется в 4000 символов (для юникода) или 8000 символов (для однобайтовых кодировок). Я подозреваю, что это источник путаницы здесь.
redcalx

Ответы:

235

Я так понимаю, что для NVARCHAR(MAX)

Ваше понимание неверно. nvarchar(max)может хранить до (а иногда и больше) 2 ГБ данных (1 миллиард двухбайтовых символов).

Из nchar и nvarchar в Книгах онлайн грамматика

nvarchar [ ( n | max ) ]

Символ |означает, что это альтернативы. т.е. вы указываете либо n буквальное, либо буквальное max.

Если вы решите указать конкретное значение, nоно должно быть от 1 до 4000, но использование maxопределяет его как тип данных большого объекта (замена ntextкоторого устарела).

Фактически в SQL Server 2008 кажется, что для переменной ограничение в 2 ГБ может быть превышено на неопределенное время при условии наличия достаточного места в tempdb( показано здесь )

Что касается других частей вашего вопроса

Усечение при объединении зависит от типа данных.

  1. varchar(n) + varchar(n) будет обрезано до 8000 символов.
  2. nvarchar(n) + nvarchar(n) будет обрезано до 4000 символов.
  3. varchar(n) + nvarchar(n)будет обрезано до 4000 символов. nvarcharимеет более высокий приоритет, поэтому результатnvarchar(4,000)
  4. [n]varchar(max)+ [n]varchar(max)не обрезается (для <2 ГБ).
  5. varchar(max)+ varchar(n)не будет усекаться (для <2 ГБ), и результат будет напечатан как varchar(max).
  6. varchar(max)+ nvarchar(n)не будет усекаться (для <2 ГБ), и результат будет напечатан как nvarchar(max).
  7. nvarchar(max)+ varchar(n)сначала преобразует varchar(n)ввод, nvarchar(n)а затем выполнит конкатенацию. Если длина varchar(n)строки превышает 4000 символов, будет выполнено приведение nvarchar(4000)и произойдет усечение .

Типы данных строковых литералов

Если вы используете Nпрефикс и длина строки <= 4000 символов, она будет набрана как nvarchar(n)где n- длина строки. Так N'Foo'будем относиться nvarchar(3)к примеру. Если строка длиннее 4000 символов, она будет рассматриваться какnvarchar(max)

Если вы не используете Nпрефикс и длина строки <= 8000 символов, она будет набрана как varchar(n)где n- длина строки. Если дольше, какvarchar(max)

В обоих случаях, если длина строки равна нулю, nустанавливается значение 1.

Новые элементы синтаксиса.

1.CONCAT функция здесь не поможет

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

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

Приведенное выше возвращает 8000 для обоих методов конкатенации.

2. Будьте осторожны с+=

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

Возврат

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

Обратите внимание, что @Aобнаружено усечение.

Как решить возникшую проблему.

Вы получаете усечение либо потому, что вы объединяете два не maxтипа данных вместе, либо потому, что вы объединяете varchar(4001 - 8000)строку с nvarcharтипизированной строкой (даже nvarchar(max)).

Чтобы избежать второй проблемы, просто убедитесь, что все строковые литералы (или хотя бы те, длина которых находится в диапазоне от 4001 до 8000) имеют префикс N.

Чтобы избежать первой проблемы, измените назначение с

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

Чтобы

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

так что an NVARCHAR(MAX)участвует в конкатенации с самого начала (поскольку результат каждой конкатенации также NVARCHAR(MAX)будет распространяться)

Избегайте усечения при просмотре

Убедитесь, что у вас выбран режим «результаты в сетку», тогда вы можете использовать

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

Параметры SSMS позволяют установить неограниченную длину XMLрезультатов. processing-instructionБит позволяет избежать проблем с символами , такими как <показ , как &lt;.

Мартин Смит
источник
2
@Killercam - Возможно, вы получите неявное приведение в nvarchar(4000)действие. Если строковый литерал меньше 4000 символов, он обрабатывается как nvarchar(x). Присоединение к нему другого nvarchar(x)значения будет усекать, а не повышать доnvarchar(max)
Мартин Смит
2
@Killercam - Вы, вероятно, получаете усечение согласно моему первому комментарию. Попробуйте изменить назначение на, DECLARE @SQL NVARCHAR(MAX) = ''; SET @SQL = @SQL + чтобы NVARCHAR(MAX)в конкатенации участвовал объект .
Мартин Смит,
2
@Killercam - Вероятно, у вас есть строка от 4000 до 8000 символов. С Nпрефиксом, который будет обрабатываться как nvarchar(max)без него, он будет обрабатываться как varchar(n)неявно приведенный к, nvarchar(4000)когда вы соединяетесь сnvarchar
Мартин Смит
3
этот ответ меня просветил
Мудассир Хасан
1
Отличный ответ. Спасибо!
Джон Белл
6

Хорошо, поэтому, если позже проблема в том, что у вас есть запрос, превышающий допустимый размер (что может произойти, если он будет продолжать расти), вам придется разбить его на куски и выполнить строковые значения. Итак, допустим, у вас есть хранимая процедура, подобная следующей:

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

Вы также можете использовать текст nvarchar. это значит, что у вас должна быть просто буква «N» перед массивной строкой, и все! больше нет ограничений

DELARE @SQL NVARCHAR(MAX);
SET @SQL = N'SomeMassiveString > 4000 chars...';
EXEC(@SQL);
GO
Максимум
источник
3
Это не вся картина ... Если вы используете префикс N и nvarchar(n)длина строки <= 4000 символов, она будет набрана как где n - длина строки. Так что N'Foo будет рассматриваться, как nvarchar(3)например. Если строка длиннее 4000 символов, она будет рассматриваться как nvarchar(max). Если вы не используете префикс N и varchar(n)длина строки <= 8000 символов, она будет набрана как где n - длина строки. Если дольше как varchar(max). Для обоих вышеперечисленных, если длина строки равна нулю, тогда n устанавливается в 1.
MoonKnight
1

Принятый ответ помог мне, но я сбился с пути, выполняя конкатенацию varchars, включающих операторы case. Я знаю, что вопрос OP не включает в себя операторы case, но я подумал, что было бы полезно опубликовать здесь для других, подобных мне, которые оказались здесь, пытаясь построить длинные динамические операторы SQL, включающие операторы case.

При использовании операторов case с конкатенацией строк правила, упомянутые в принятом ответе, применяются к каждому разделу оператора case независимо.

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
Heta77
источник