Как распечатать VARCHAR (MAX) с помощью оператора печати?


108

У меня есть код:

DECLARE @Script VARCHAR(MAX)

SELECT @Script = definition FROM manged.sys.all_sql_modules sq
where sq.object_id = (SELECT object_id from managed.sys.objects 
Where type = 'P' and Name = 'usp_gen_data')

Declare @Pos int

SELECT  @pos=CHARINDEX(CHAR(13)+CHAR(10),@script,7500)

PRINT SUBSTRING(@Script,1,@Pos)

PRINT SUBSTRING(@script,@pos,8000)

Длина скрипта составляет около 10 000 символов, и поскольку я использую оператор печати, который может содержать не более 8000 символов, я использую два оператора печати.

Проблема в том, что у меня есть сценарий, который, скажем, состоит из 18000 символов, тогда я использовал 3 оператора печати.

Итак, есть ли способ установить количество операторов печати в зависимости от длины скрипта?


1
Вам нужно использовать PRINTили вы открыты для других альтернатив?
Мартин Смит

Я бы предложил создать (или найти и проголосовать) по проблеме на connect.microsoft.com/SQLServer/Feedback
jmoreno,

Ответы:


23

Вы можете сделать WHILEцикл на основе счетчика длины вашего скрипта, разделенного на 8000.

НАПРИМЕР:

DECLARE @Counter INT
SET @Counter = 0
DECLARE @TotalPrints INT
SET @TotalPrints = (LEN(@script) / 8000) + 1
WHILE @Counter < @TotalPrints 
BEGIN
    -- Do your printing...
    SET @Counter = @Counter + 1
END

Если вы посмотрите на мой код, я также использую переменную @Pos, чтобы найти разрыв строки и соответственно распечатать. Итак, как я могу использовать это в вашем коде.
Питер

@peter Вы можете просто взять текущий SUBSTRи посмотреть только на ту часть, с которой вы имеете дело в то время, и повторить это, или если вы знаете, что каждый раз будет разрыв строки до ограничения 8k, тогда просто сделайте на WHILEоснове найденной строки перерывы.
Келси

@peter можете ли вы зацикливаться на разрывах строк? Например, ищите разрыв строки, если он найден, печать до разрыва строки, substr от разрыва строки до следующих 8k символов, поиск, печать, новая substr и т. Д.?
Келси

1
Функция LEN (), а не LENGTH ()
shiggity

8
Раньше я print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000))печатал свой сценарий.
Лукас Тум

217

Я знаю, что это старый вопрос, но то, что я сделал, здесь не упоминается.

Для меня сработало следующее.

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)

4
@gordy - Мне кажется, что в SSMS этот метод не особо работает.
Jirka Hanika

1
У меня это работает на SQL 2008 R2 SP2 (10.50.1600) с использованием CAST () или CONVERT (), а также на SQL 2008 SP2 (10.0.5500).

26
Я вижу усечение после 16002 символа, но все же длиннее, чем было max. DECLARE @info NVARCHAR(MAX) = 'A';SET @info = REPLICATE(@info, 16000) + 'BC This is not printed';PRINT @info;PRINT CAST(@info AS NTEXT);
Мартин Смит

6
Типы данных ntext, text и image будут удалены в будущей версии Microsoft SQL Server. Избегайте использования этих типов данных в новых разработках и запланируйте изменение приложений, которые в настоящее время их используют.
jumxozizi

5
Не работал у меня в SQL Server Management Studio для SQL Server 2014. Он сокращается после 16 000 символов. Написано Мартином Смитом.
Jana Weschenfelder

103

В следующем обходном пути этот PRINTоператор не используется . Он хорошо работает в сочетании с SQL Server Management Studio.

SELECT CAST('<root><![CDATA[' + @MyLongString + ']]></root>' AS XML)

Вы можете щелкнуть возвращенный XML, чтобы развернуть его во встроенной программе просмотра XML.

На стороне клиента существует довольно щедрое ограничение на отображаемый размер. При необходимости перейдите к, Tools/Options/Query Results/SQL Server/Results to Grid/XML dataчтобы отрегулировать его.


11
+1. Но этот метод кодирует символы, которые имеют особое значение в XML. Например, <заменяется на &lt;.
Iain Samuel McLean Elder

5
вы можете написать сценарий без <root>....таких:SELECT CAST(@MyLongString AS XML)
ali youhannaei

2
@aliyouhannaei - Да и нет. Вы правы, что корневой элемент не является обязательным. Но без раздела CDATA у вашего метода возникают проблемы с некоторыми строками. Особенно те, которые содержат <. Если это не XML, запрос обычно выдает ошибку. Если это XML, строка может быть переформатирована в другую «эквивалентную» XML-форму.
Jirka Hanika

8
@IainElder - Это хороший аргумент, и Адам Мачаник предлагает обходное решение . Именно это: SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH(''). Строка будет заключена в PI с именем «x», но PI не будет заключена в другой элемент (из-за PATH('')).
Jirka Hanika

Это не сработает для очень длинных текстов, даже если для параметра «Максимальное количество
извлеченных

39

Вот как это нужно делать:

DECLARE @String NVARCHAR(MAX);
DECLARE @CurrentEnd BIGINT; /* track the length of the next substring */
DECLARE @offset tinyint; /*tracks the amount of offset needed */
set @string = replace(  replace(@string, char(13) + char(10), char(10))   , char(13), char(10))

WHILE LEN(@String) > 1
BEGIN
    IF CHARINDEX(CHAR(10), @String) between 1 AND 4000
    BEGIN
           SET @CurrentEnd =  CHARINDEX(char(10), @String) -1
           set @offset = 2
    END
    ELSE
    BEGIN
           SET @CurrentEnd = 4000
            set @offset = 1
    END   
    PRINT SUBSTRING(@String, 1, @CurrentEnd) 
    set @string = SUBSTRING(@String, @CurrentEnd+@offset, LEN(@String))   
END /*End While loop*/

Взято из http://ask.sqlservercentral.com/questions/3102/any-way-around-the-print-limit-of-nvarcharmax-in-s.html


1
Отличная техника! Кстати, настоящая статья, в которой использовался этот метод, была
взята

2
Это сработало для меня, но также разрезало одно из моих имен полей пополам. Итак, если я использовал этот метод для PRINT (@string), а затем EXECUTE (@string), EXECUTE завершился ошибкой.
Джонни Бонс,

1
Это не работает для меня, поскольку функция PRINT добавляет разрывы строк в плохих местах и ​​потребует большей очистки, чем она того стоит, но это наиболее близкое решение проблемы.
Randy Burden

14

Столкнулся с этим вопросом и захотелось чего-то более простого ... Попробуйте следующее:

SELECT [processing-instruction(x)]=@Script FOR XML PATH(''),TYPE

5
Проще было бы, SELECT CAST(@STMT AS XML)как уже было сказано в другом комментарии. Производит точно такой же вывод и действительно менее сложен, чем создание хранимой процедуры для вывода.
Феликс Байер

4
@Felix Хотя это было бы намного проще, для SQL это не совсем работает. Приведение в XML пытается преобразовать текст SQL в XML. Он заменит <,> и & на & lt ;, & gt; и & amp; и он не будет обрабатывать символы, недопустимые в XML. Кроме того, если у вас есть ситуация, когда вы сравниваете <и затем>, он считает, что это элемент, и выдает ошибку недопустимого узла.
Едын

12

Этот процесс правильно распечатывает VARCHAR(MAX)параметр с учетом упаковки:

CREATE PROCEDURE [dbo].[Print]
    @sql varchar(max)
AS
BEGIN
    declare
        @n int,
        @i int = 0,
        @s int = 0, -- substring start posotion
        @l int;     -- substring length

    set @n = ceiling(len(@sql) / 8000.0);

    while @i < @n
    begin
        set @l = 8000 - charindex(char(13), reverse(substring(@sql, @s, 8000)));
        print substring(@sql, @s, @l);
        set @i = @i + 1;
        set @s = @s + @l + 2; -- accumulation + CR/LF
    end

    return 0
END

эта процедура конфликтует с символами Unicode. как обрабатывать utf8 например?
mostafa8026

в ответе на комментарий выше это можно сделать, изменив тип @script на nvarchar.
mostafa8026

8

Я хотел использовать оператор печати для отладки некоторого динамического sql, поскольку я представляю, что большинство из вас использует печать по тем же причинам.

Я попробовал несколько из перечисленных решений и обнаружил, что решение Келси работает с небольшими изменениями (@sql - мой @script) nb LENGTH не является допустимой функцией:

--http://stackoverflow.com/questions/7850477/how-to-print-varcharmax-using-print-statement
--Kelsey
DECLARE @Counter INT
SET @Counter = 0
DECLARE @TotalPrints INT
SET @TotalPrints = (LEN(@sql) / 4000) + 1
WHILE @Counter < @TotalPrints 
BEGIN
    PRINT SUBSTRING(@sql, @Counter * 4000, 4000)
    SET @Counter = @Counter + 1
END
PRINT LEN(@sql)

Этот код делает, как прокомментировано, добавляет новую строку в вывод, но для отладки это не проблема для меня.

Решение Бена Б. идеально и является наиболее элегантным, хотя для отладки требуется много строк кода, поэтому я решил использовать свою небольшую модификацию решения Келси. Возможно, стоит создать систему, подобную хранимой процедуре в msdb, для кода Бена Б., которую можно было бы повторно использовать и вызывать в одной строке?

Код Алфокса, к сожалению, не работает, потому что это было бы проще.


Я просто добавил решение Бена Б. как временную хранимую процедуру. Мои сценарии немного чище, но я согласен, что для отладки требуется много строк.
Зарепет

4

Вы можете использовать это

declare @i int = 1
while Exists(Select(Substring(@Script,@i,4000))) and (@i < LEN(@Script))
begin
     print Substring(@Script,@i,4000)
     set @i = @i+4000
end

4

Я только что создал SP из отличного ответа Бена :

/*
---------------------------------------------------------------------------------
PURPOSE   : Print a string without the limitation of 4000 or 8000 characters.
/programming/7850477/how-to-print-varcharmax-using-print-statement
USAGE     : 
DECLARE @Result NVARCHAR(MAX)
SET @Result = 'TEST'
EXEC [dbo].[Print_Unlimited] @Result
---------------------------------------------------------------------------------
*/
ALTER PROCEDURE [dbo].[Print_Unlimited]
    @String NVARCHAR(MAX)
AS

BEGIN

    BEGIN TRY
    ---------------------------------------------------------------------------------

    DECLARE @CurrentEnd BIGINT; /* track the length of the next substring */
    DECLARE @Offset TINYINT; /* tracks the amount of offset needed */
    SET @String = replace(replace(@String, CHAR(13) + CHAR(10), CHAR(10)), CHAR(13), CHAR(10))

    WHILE LEN(@String) > 1
    BEGIN
        IF CHARINDEX(CHAR(10), @String) BETWEEN 1 AND 4000
        BEGIN
            SET @CurrentEnd =  CHARINDEX(CHAR(10), @String) -1
            SET @Offset = 2
        END
        ELSE
        BEGIN
            SET @CurrentEnd = 4000
            SET @Offset = 1
        END   
        PRINT SUBSTRING(@String, 1, @CurrentEnd) 
        SET @String = SUBSTRING(@String, @CurrentEnd + @Offset, LEN(@String))   
    END /*End While loop*/

    ---------------------------------------------------------------------------------
    END TRY
    BEGIN CATCH
        DECLARE @ErrorMessage VARCHAR(4000)
        SELECT @ErrorMessage = ERROR_MESSAGE()    
        RAISERROR(@ErrorMessage,16,1)
    END CATCH
END

Замечательно, именно то, что я искал!
kooch

3
создать процедуру dbo.PrintMax @text nvarchar (max)
так как
начать
    объявить @i int, @newline nchar (2), @print varchar (max); 
    установить @newline = nchar (13) + nchar (10);
    выберите @i = charindex (@newline, @text);
    пока (@i> 0)
    начать
        выберите @print = substring (@ text, 0, @ i);
        пока (len (@print)> 8000)
        начать
            подстрока печати (@ print, 0,8000);
            выберите @print = substring (@ print, 8000, len (@print));
        конец
        print @print;
        выберите @text = substring (@ text, @ i + 2, len (@text));
        выберите @i = charindex (@newline, @text);
    конец
    print @text;
конец

2

Есть отличная функция PrintMax, написанная Беннеттом Диллом .

Вот немного измененная версия, которая использует временную хранимую процедуру, чтобы избежать "загрязнения схемы" (идея из https://github.com/Toolien/sp_GenMerge/blob/master/sp_GenMerge.sql )

EXEC (N'IF EXISTS (SELECT * FROM tempdb.sys.objects 
                   WHERE object_id = OBJECT_ID(N''tempdb..#PrintMax'') 
                   AND type in (N''P'', N''PC''))
    DROP PROCEDURE #PrintMax;');
EXEC (N'CREATE PROCEDURE #PrintMax(@iInput NVARCHAR(MAX))
AS
BEGIN
    IF @iInput IS NULL
    RETURN;

    DECLARE @ReversedData NVARCHAR(MAX)
          , @LineBreakIndex INT
          , @SearchLength INT;

    SET @SearchLength = 4000;

    WHILE LEN(@iInput) > @SearchLength
    BEGIN
    SET @ReversedData = LEFT(@iInput COLLATE DATABASE_DEFAULT, @SearchLength);
    SET @ReversedData = REVERSE(@ReversedData COLLATE DATABASE_DEFAULT);
    SET @LineBreakIndex = CHARINDEX(CHAR(10) + CHAR(13),
                          @ReversedData COLLATE DATABASE_DEFAULT);
    PRINT LEFT(@iInput, @SearchLength - @LineBreakIndex + 1);
    SET @iInput = RIGHT(@iInput, LEN(@iInput) - @SearchLength 
                        + @LineBreakIndex - 1);
    END;

    IF LEN(@iInput) > 0
    PRINT @iInput;
END;');

Демо DBFiddle

РЕДАКТИРОВАТЬ:

С помощью CREATE OR ALTERможно было избежать двух вызовов EXEC:

EXEC (N'CREATE OR ALTER PROCEDURE #PrintMax(@iInput NVARCHAR(MAX))
AS
BEGIN
    IF @iInput IS NULL
    RETURN;

    DECLARE @ReversedData NVARCHAR(MAX)
          , @LineBreakIndex INT
          , @SearchLength INT;

    SET @SearchLength = 4000;

    WHILE LEN(@iInput) > @SearchLength
    BEGIN
    SET @ReversedData = LEFT(@iInput COLLATE DATABASE_DEFAULT, @SearchLength);
    SET @ReversedData = REVERSE(@ReversedData COLLATE DATABASE_DEFAULT);
    SET @LineBreakIndex = CHARINDEX(CHAR(10) + CHAR(13), @ReversedData COLLATE DATABASE_DEFAULT);
    PRINT LEFT(@iInput, @SearchLength - @LineBreakIndex + 1);
    SET @iInput = RIGHT(@iInput, LEN(@iInput) - @SearchLength + @LineBreakIndex - 1);
    END;

    IF LEN(@iInput) > 0
    PRINT @iInput;
END;');

db <> fiddle Demo


2

Использует перевод строки и пробелы в качестве хорошей точки останова:

declare @sqlAll as nvarchar(max)
set @sqlAll = '-- Insert all your sql here'

print '@sqlAll - truncated over 4000'
print @sqlAll
print '   '
print '   '
print '   '

print '@sqlAll - split into chunks'
declare @i int = 1, @nextspace int = 0, @newline nchar(2)
set @newline = nchar(13) + nchar(10)


while Exists(Select(Substring(@sqlAll,@i,3000))) and (@i < LEN(@sqlAll))
begin
    while Substring(@sqlAll,@i+3000+@nextspace,1) <> ' ' and Substring(@sqlAll,@i+3000+@nextspace,1) <> @newline
    BEGIN
        set @nextspace = @nextspace + 1
    end
    print Substring(@sqlAll,@i,3000+@nextspace)
    set @i = @i+3000+@nextspace
    set @nextspace = 0
end
print '   '
print '   '
print '   '


2

Или просто:

PRINT SUBSTRING(@SQL_InsertQuery, 1, 8000)
PRINT SUBSTRING(@SQL_InsertQuery, 8001, 16000)

0

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

CREATE PROCEDURE [Internal].[LongPrint]
    @msg nvarchar(max)
AS
BEGIN

    -- SET NOCOUNT ON reduces network overhead
    SET NOCOUNT ON;

    DECLARE @MsgLen int;
    DECLARE @CurrLineStartIdx int = 1;
    DECLARE @CurrLineEndIdx int;
    DECLARE @CurrLineLen int;   
    DECLARE @SkipCount int;

    -- Normalise line end characters.
    SET @msg = REPLACE(@msg, char(13) + char(10), char(10));
    SET @msg = REPLACE(@msg, char(13), char(10));

    -- Store length of the normalised string.
    SET @MsgLen = LEN(@msg);        

    -- Special case: Empty string.
    IF @MsgLen = 0
    BEGIN
        PRINT '';
        RETURN;
    END

    -- Find the end of next substring to print.
    SET @CurrLineEndIdx = CHARINDEX(CHAR(10), @msg);
    IF @CurrLineEndIdx BETWEEN 1 AND 4000
    BEGIN
        SET @CurrLineEndIdx = @CurrLineEndIdx - 1
        SET @SkipCount = 2;
    END
    ELSE
    BEGIN
        SET @CurrLineEndIdx = 4000;
        SET @SkipCount = 1;
    END     

    -- Loop: Print current substring, identify next substring (a do-while pattern is preferable but TSQL doesn't have one).
    WHILE @CurrLineStartIdx < @MsgLen
    BEGIN
        -- Print substring.
        PRINT SUBSTRING(@msg, @CurrLineStartIdx, (@CurrLineEndIdx - @CurrLineStartIdx)+1);

        -- Move to start of next substring.
        SET @CurrLineStartIdx = @CurrLineEndIdx + @SkipCount;

        -- Find the end of next substring to print.
        SET @CurrLineEndIdx = CHARINDEX(CHAR(10), @msg, @CurrLineStartIdx);
        SET @CurrLineLen = @CurrLineEndIdx - @CurrLineStartIdx;

        -- Find bounds of next substring to print.              
        IF @CurrLineLen BETWEEN 1 AND 4000
        BEGIN
            SET @CurrLineEndIdx = @CurrLineEndIdx - 1
            SET @SkipCount = 2;
        END
        ELSE
        BEGIN
            SET @CurrLineEndIdx = @CurrLineStartIdx + 4000;
            SET @SkipCount = 1;
        END
    END
END

0

Это должно работать правильно, это просто улучшение предыдущих ответов.

DECLARE @Counter INT
DECLARE @Counter1 INT
SET @Counter = 0
SET @Counter1 = 0
DECLARE @TotalPrints INT
SET @TotalPrints = (LEN(@QUERY) / 4000) + 1
print @TotalPrints 
WHILE @Counter < @TotalPrints 
BEGIN
-- Do your printing...
print(substring(@query,@COUNTER1,@COUNTER1+4000))

set @COUNTER1 = @Counter1+4000
SET @Counter = @Counter + 1
END

0

Если в исходном коде не будет проблем с заменой LF на CRLF, не требуется отладка, следуя простым выводам кода.

--http://stackoverflow.com/questions/7850477/how-to-print-varcharmax-using-print-statement
--Bill Bai
SET @SQL=replace(@SQL,char(10),char(13)+char(10))
SET @SQL=replace(@SQL,char(13)+char(13)+char(10),char(13)+char(10) )
DECLARE @Position int 
WHILE Len(@SQL)>0 
BEGIN
SET @Position=charindex(char(10),@SQL)
PRINT left(@SQL,@Position-2)
SET @SQL=substring(@SQL,@Position+1,len(@SQL))
end; 

0

Моя версия PrintMax для предотвращения плохих разрывов строк при выводе:


    CREATE PROCEDURE [dbo].[PrintMax](@iInput NVARCHAR(MAX))
    AS
    BEGIN
      Declare @i int;
      Declare @NEWLINE char(1) = CHAR(13) + CHAR(10);
      While LEN(@iInput)>0 BEGIN
        Set @i = CHARINDEX(@NEWLINE, @iInput)
        if @i>8000 OR @i=0 Set @i=8000
        Print SUBSTRING(@iInput, 0, @i)
        Set @iInput = SUBSTRING(@iInput, @i+1, LEN(@iInput))
      END
    END
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.