Мое поле VARCHAR(MAX) ограничивается 4000; что дает?

У меня есть таблица в одной из моих баз данных, которая представляет собой очередь электронных писем. Электронные письма на определенные адреса собираются в одно электронное письмо, что выполняется с помощью sproc. В sproc у меня есть табличная переменная, которую я использую для создания накопленных тел электронных писем, а затем циклически отправляю каждое электронное письмо. В моей таблице var у меня есть столбец body, определенный как VARCHAR(MAX), поскольку для данного адреса электронной почты может быть любое количество электронных писем, накопленных в настоящее время. Однако кажется, что даже несмотря на то, что мой столбец определен как VARCHAR(MAX), он ведет себя так, как если бы он был VARCHAR(4000), и усекает входящие в него данные, хотя он НЕ создает какие-либо исключения, он просто молча прекращает объединение любых больше данных после 4000 символов.

Оператор MERGE — это то, где он создает накопленное тело электронной почты в @EMAILS.BODY, поле, которое усекается до 4000 символов.

ИЗМЕНИТЬ

Я обновил свой оператор MERGE, пытаясь преобразовать всю назначенную строку в VARCHAR(MAX), но он все еще молча усекает себя до 4000 символов... вот мой новый MERGE:

MERGE @EMAILS AS DST 
USING (SELECT * FROM @ROWS WHERE ROWID = @CURRID) AS SRC 
ON SRC.ADDRESS = DST.ADDRESS 
WHEN MATCHED THEN 
    UPDATE SET 
        DST.ALLIDS = DST.ALLIDS + ', ' + CONVERT(VARCHAR,ROWID), 
        DST.BODY = DST.BODY + 
            CONVERT(VARCHAR(MAX),
                '<i>'+CONVERT(VARCHAR,SRC.DATED,101)+
                ' '+CONVERT(VARCHAR,SRC.DATED,8)+
                ':</i> <b>'+SRC.SUBJECT+'</b>'+CHAR(13)+
                SRC.BODY+' (Message ID '+
                CONVERT(VARCHAR,SRC.ROWID)+')'+
                CHAR(13)+CHAR(13)
            )
WHEN NOT MATCHED BY TARGET THEN 
    INSERT (ADDRESS, ALLIDS, BODY) VALUES (
        SRC.ADDRESS, 
        CONVERT(VARCHAR,ROWID), 
        CONVERT(VARCHAR(MAX),
            '<i>'+CONVERT(VARCHAR,SRC.DATED,101)+
            ' '+CONVERT(VARCHAR,SRC.DATED,8)+
            ':</i> <b>'+SRC.SUBJECT+'</b>'+CHAR(13)+
            SRC.BODY+' (Message ID '+CONVERT(VARCHAR,SRC.ROWID)+')'
            +CHAR(13)+CHAR(13)
        )
    );

ЗАВЕРШИТЬ РЕДАКТИРОВАНИЕ

Ниже приведен код моего sproc...

ALTER PROCEDURE [system].[SendAccumulatedEmails]
AS 
BEGIN
    SET NOCOUNT ON;

    DECLARE @SENTS  BIGINT = 0;

    DECLARE @ROWS TABLE (
        ROWID    ROWID, 
        DATED    DATETIME, 
        ADDRESS  NAME, 
        SUBJECT  VARCHAR(1000), 
        BODY     VARCHAR(MAX)
    )
    INSERT INTO @ROWS SELECT ROWID, DATED, ADDRESS, SUBJECT, BODY 
    FROM system.EMAILQUEUE 
        WHERE ACCUMULATE = 1 AND SENT IS NULL
        ORDER BY ADDRESS, DATED

    DECLARE @EMAILS TABLE (
        ADDRESS  NAME, 
        ALLIDS   VARCHAR(1000),
        BODY     VARCHAR(MAX) 
    )

    DECLARE @PRVRID ROWID = NULL, @CURRID ROWID = NULL
    SELECT @CURRID = MIN(ROWID) FROM @ROWS
    WHILE @CURRID IS NOT NULL BEGIN
        MERGE @EMAILS AS DST 
        USING (SELECT * FROM @ROWS WHERE ROWID = @CURRID) AS SRC 
        ON SRC.ADDRESS = DST.ADDRESS 
        WHEN MATCHED THEN 
            UPDATE SET 
                DST.ALLIDS = DST.ALLIDS + ', ' + CONVERT(VARCHAR,ROWID), 
                DST.BODY = DST.BODY + '<i>'+CONVERT(VARCHAR,SRC.DATED,101)+' '
                            +CONVERT(VARCHAR,SRC.DATED,8)
                            +':</i> <b>'+SRC.SUBJECT+'</b>'+CHAR(13)+SRC.BODY
                            +' (Message ID '+CONVERT(VARCHAR,SRC.ROWID)+')'
                            +CHAR(13)+CHAR(13)
        WHEN NOT MATCHED BY TARGET THEN 
            INSERT (ADDRESS, ALLIDS, BODY) VALUES (
                SRC.ADDRESS, 
                CONVERT(VARCHAR,ROWID), 
                '<i>'+CONVERT(VARCHAR,SRC.DATED,101)+' '
                    +CONVERT(VARCHAR,SRC.DATED,8)+':</i> <b>'
                    +SRC.SUBJECT+'</b>'+CHAR(13)+SRC.BODY
                    +' (Message ID '+CONVERT(VARCHAR,SRC.ROWID)+')'
                    +CHAR(13)+CHAR(13));

        SELECT @PRVRID = @CURRID, @CURRID = NULL
        SELECT @CURRID = MIN(ROWID) FROM @ROWS WHERE ROWID > @PRVRID
    END 

    DECLARE @MAILFROM VARCHAR(100) = system.getOption('MAILFROM'), 
    DECLARE @SMTPHST VARCHAR(100) = system.getOption('SMTPSERVER'), 
    DECLARE @SMTPUSR VARCHAR(100) = system.getOption('SMTPUSER'), 
    DECLARE @SMTPPWD VARCHAR(100) = system.getOption('SMTPPASS')

    DECLARE @ADDRESS NAME, @BODY VARCHAR(MAX), @ADDL VARCHAR(MAX)
    DECLARE @SUBJECT VARCHAR(1000) = 'Accumulated Emails from LIJSL'

    DECLARE @PRVID NAME = NULL, @CURID NAME = NULL 
    SELECT @CURID = MIN(ADDRESS) FROM @EMAILS
    WHILE @CURID IS NOT NULL BEGIN
        SELECT @ADDRESS = ADDRESS, @BODY = BODY 
        FROM @EMAILS WHERE ADDRESS = @CURID

        SELECT @BODY = @BODY + 'This is an automated message sent from an unmonitored mailbox.'+CHAR(13)+'Do not reply to this message; your message will not be read.'
        SELECT @BODY = 
            '<style type="text/css">
                * {font-family: Tahoma, Arial, Verdana;}
                p {margin-top: 10px; padding-top: 10px; border-top: single 1px dimgray;} 
                p:first-child {margin-top: 10px; padding-top: 0px; border-top: none 0px transparent;}
            </style>' 
            + @BODY 

        exec system.LogIt @SUBJECT, @BODY

        BEGIN TRY 
            exec system.SendMail @SMTPHST, @SMTPUSR, @SMTPPWD, @MAILFROM, 
                             @ADDRESS, NULL, NULL, @SUBJECT, @BODY, 1
        END TRY 
        BEGIN CATCH
            DECLARE @EMSG NVARCHAR(2048) = 'system.EMAILQUEUE.AI:'+ERROR_MESSAGE()
            SELECT @ADDL = 'TO:'+@ADDRESS+CHAR(13)+'SUBJECT:'+@SUBJECT+CHAR(13)+'BODY:'+@BODY
            exec system.LogIt @EMSG,@ADDL
        END CATCH

        SELECT @PRVID = @CURID, @CURID = NULL
        SELECT @CURID = MIN(ADDRESS) FROM @EMAILS WHERE ADDRESS > @PRVID
    END

    UPDATE system.EMAILQUEUE SET SENT = getdate()
    FROM system.EMAILQUEUE E, @ROWS R WHERE E.ROWID = R.ROWID
END

person eidylon    schedule 23.03.2010    source источник
comment
Пожалуйста, смотрите мое обновление... у вас есть nvarchar справа, который неявно выполняет преобразование из varchar(max) в nvarchar(4000)   -  person gbn    schedule 23.03.2010
comment
Не позаботится ли об этом обертывание всего выражения RHS в CONVERT(VARCHAR(MAX), ‹expression›) в соответствии с моим обновлением?   -  person eidylon    schedule 23.03.2010
comment
Единственное, что я вижу, что явно не преобразуется в VARCHAR(MAX), это буквальные строки, такие как '‹i›'. Используются ли литералы как NVARCHAR? Все поля SRC.‹› преобразуются в VARCHAR, а функция CHAR возвращает тип CHAR.   -  person eidylon    schedule 23.03.2010


Ответы (4)


Исправлено...

Таблица может быть varchar(max), но значения, которые вы назначаете, только nvarchar(4000)

Это,

maxcolumn = maxvalues + smallstring1 + **unicodestring** + smallstring3 + smallstring4 ...

В правой части останется максимум nvarchar(4000) из-за приоритет типа данных. nvarchar > varchar. При назначении максимальному столбцу он усекается

Вам нужно будет убедиться, что все значения справа в varchar

Это все еще похоже на целочисленное деление... что меня смутило, так это ограничение в 4000, когда varchar равно 8000... где-то это подразумевает nvarchar.

Для Nvarchar(Max) я только получить 4000 символов в TSQL?

person gbn    schedule 23.03.2010
comment
Итак, в операторе MERGE устанавливается DST.BODY = DST.BODY + ‹smallstrings›. Поскольку DST.BODY находится справа и определяется VARCHAR(MAX), разве это не соответствует этому? - person eidylon; 23.03.2010
comment
@eidylon: как насчет ВСТАВКИ...? - person gbn; 23.03.2010
comment
@gbn - SRC.BODY должен продвигать все выражение до MAX, если я правильно читаю MSDN. - person Jeffrey L Whitledge; 23.03.2010
comment
Мое ОБНОВЛЕНИЕ теперь выполняет DST.BODY = DST.BODY + CONVERT(VARCHAR(MAX),‹smallstrings›), а INSERT аналогичным образом вставляет CONVERT(VARCHAR(MAX),‹smallstrings›) в DST.BODY, но все еще усекается. - person eidylon; 23.03.2010
comment
Если результат объединения строк превышает ограничение в 8000 байт, результат усекается. Однако если хотя бы одна из объединенных строк имеет тип большого значения, усечение не происходит. - person Jeffrey L Whitledge; 23.03.2010
comment
@Jeffrey Джеффри - хммм, тогда я думаю, что мой код должен работать. Случай INSERT уверенно никогда не вставляет более 400 символов, а в случае UPDATE одна из конкатенированных строк (а именно DST.BODY в RHS) является большим типом. - person eidylon; 23.03.2010
comment
@Jeffrey L Whitledge, @eidylon: Хорошо, я был частично прав... сейчас обновлено - person gbn; 23.03.2010
comment
Настоящим уроком здесь является то, что varchar(max) обычно кажется ведет себя как varchar, но иногда ведет себя как совершенно другой тип данных. Конкатенация — один из таких случаев. Насколько я знаю, вам всегда нужно указывать varchar(max) для конвертируемых, если это то, что вам нужно. - person Jim L; 23.03.2010
comment
Я искал проблему приоритета с NVARCHAR, но пропустил ее. Думаю, мне не следует полагаться на свои глаза при поиске текстовых строк! - person Jeffrey L Whitledge; 23.03.2010
comment
@eidylon - Решение, как всегда, состоит в том, чтобы использовать Unicode для всего! Я не использовал 8-битную кодовую страницу уже много лет и никогда не оглядываюсь назад! :-) - person Jeffrey L Whitledge; 23.03.2010

http://blogs.infosupport.com/blogs/marks/archive/2011/03/22/take-your-varchar-to-the-max.aspx?CommentPosted=true#commentmessage

Эта проблема и ее решение очень хорошо объяснены в приведенной выше статье, решение состоит в том, чтобы добавить к конкатенации VARCHAR(MAX)

AS IN

DECLARE @SQL VARCHAR(MAX) SET @SQL = '' SET @SQL = @SQL + 'xxxxxx(n)'

person Aedna    schedule 09.08.2011
comment
Кто бы поверил! Какое странное поведение! Спасибо, что прояснили это. - person Dale K; 21.05.2014

Я подозреваю, что проблема заключается в операциях строки и преобразования. Попробуйте изменить свои преобразования на VARCHAR(max) или преобразовать все выражение в VARCHAR(max).

person Peter Ruderman    schedule 23.03.2010
comment
Хорошо, я попытался обернуть все выражение, присваиваемое/объединяемое с DST.BODY, в max как CONVERT(VARCHAR(MAX),‹bodyExpression›), и оно по-прежнему молча усекает все, что превышает 4000. - person eidylon; 23.03.2010

gbn и Джеффри, спасибо за помощь, вы помогли мне двигаться в правильном направлении. Хотя после некоторого ведения журнала и проверки, на самом деле он отлично объединяет мою строку.

Проблема заключалась не в типе данных или длине моего столбца, а в вызове моей процедуры .NET SendMail, которая принимает только NVARCHAR(4000) в качестве аргумента BODY... очевидный перевод типа .NET SqlString.

Так что теперь я иду на охоту, чтобы понять, как передать более длинные строки в функцию сборки CLR.

person eidylon    schedule 23.03.2010
comment
К сведению всех заинтересованных... передача поля NVARCHAR(MAX) в sproc CLR выполняется путем украшения определенного параметра атрибутом ‹SqlFacet(MaxSize:=-1)› - person eidylon; 24.03.2010
comment
То есть вы на самом деле не знали, что с данными в таблице все в порядке? Вы никогда не думали делать ЛЕН на нем? Так что вопрос неправильный и вводящий в заблуждение... - person gbn; 24.03.2010
comment
Я полагал, явно ошибочно, что это должна быть проблема с кодом T-SQL. Я не думал смотреть на код .NET, потому что когда дело доходит до .NET, я привык иметь дело только со строками, где длина не имеет значения. Я не много занимался интеграцией .NET/CLR-SQL, поэтому и не подумал туда заглянуть. У всех нас были дни, когда мы упускали очевидное из-за того, что оно смотрело нам прямо в лицо, и нуждались в свежем взгляде, чтобы указать нам правильное направление. - person eidylon; 24.03.2010