Como imprimir VARCHAR (MAX) usando Print Statement?

108

Eu tenho um código que é:

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)

O comprimento do Script é de cerca de 10.000 caracteres e, como estou usando a declaração de impressão, ela pode conter no máximo 8.000. Portanto, estou usando duas declarações de impressão.

O problema é quando eu tenho um script que tem, digamos, 18.000 caracteres, então eu costumava usar 3 instruções de impressão.

Portanto, há uma maneira de definir o número de declarações de impressão dependendo do comprimento do script?

Peter
fonte
1
Você tem que usar PRINTou está aberto a outras alternativas?
Martin Smith,
Sugiro a criação (ou localização e votação) de um problema em connect.microsoft.com/SQLServer/Feedback
jmoreno

Respostas:

23

Você poderia fazer um WHILEloop com base na contagem do comprimento do seu script dividido por 8.000.

POR EXEMPLO:

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
Kelsey
fonte
Se você olhar meu código, também estou usando a variável @Pos para encontrar a quebra de linha e imprimir de acordo. Então, como eu poderia usar isso em seu código.
peter
@peter Você pode apenas pegar a corrente SUBSTRe olhar apenas para a parte com a qual está lidando no momento e iterar nela ou se você souber que haverá uma quebra de linha antes do limite de 8k a cada vez, então faça apenas com WHILEbase na descoberta da linha rompe.
Kelsey de
@peter você pode fazer um loop com base nas quebras de linha? Por exemplo, procurar quebra de linha, se for encontrado imprimir até a quebra de linha, substr da quebra de linha até os próximos 8k caracteres, pesquisar, imprimir, novo substr etc?
Kelsey de
1
A função é LEN () e não LENGTH ()
shiggity
8
Costumava print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000))imprimir meu script.
Lukas Thum,
216

Sei que é uma pergunta antiga, mas o que fiz não é mencionado aqui.

Para mim, o seguinte funcionou.

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)
alfoks
fonte
4
@gordy - Parece-me que este método não funciona realmente no SSMS.
Jirka Hanika
1
Isso funciona para mim no SQL 2008 R2 SP2 (10.50.1600) usando CAST () ou CONVERT () e no SQL 2008 SP2 (10.0.5500).
26
Vejo truncamento após 16.002 caracteres, ainda mais longo do maxque antes. DECLARE @info NVARCHAR(MAX) = 'A';SET @info = REPLICATE(@info, 16000) + 'BC This is not printed';PRINT @info;PRINT CAST(@info AS NTEXT);
Martin Smith
6
Os tipos de dados ntext, text e image serão removidos em uma versão futura do Microsoft SQL Server. Evite usar esses tipos de dados em novos trabalhos de desenvolvimento e planeje modificar os aplicativos que os usam atualmente.
jumxozizi
5
Não funcionou para mim no SQL Server Management Studio para SQL Server 2014. Corta após 16.000 caracteres. Conforme escrito por Martin Smith.
Jana Weschenfelder
103

A seguinte solução alternativa não usa a PRINTinstrução. Ele funciona bem em combinação com o SQL Server Management Studio.

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

Você pode clicar no XML retornado para expandi-lo no visualizador XML integrado.

Há um limite do lado do cliente bastante generoso no tamanho exibido. Vá para Tools/Options/Query Results/SQL Server/Results to Grid/XML datapara ajustá-lo, se necessário.

Jirka Hanika
fonte
11
+1. Mas esse método codifica caracteres que têm um significado especial em XML. Por exemplo, <é substituído por &lt;.
Iain Samuel McLean Elder
5
você pode escrever o script sem <root>....gostar de:SELECT CAST(@MyLongString AS XML)
ali youhannaei
2
@aliyouhannaei - Sim e não. Você está certo ao dizer que o elemento raiz não é estritamente necessário. Mas, sem a seção CDATA, seu método começa a ter problemas com algumas strings. Especialmente aqueles que contêm <. Se eles não forem XML, a consulta geralmente apresentará um erro. Se forem XML, a string pode acabar reformatada em outra forma XML "equivalente".
Jirka Hanika,
8
@IainElder - Esse é um bom ponto e há uma solução alternativa para ele de Adam Machanic . É este: SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH(''). A string será agrupada em um PI chamado "x", mas o PI não será agrupado em outro elemento (por causa de PATH('')).
Jirka Hanika,
Isso não funcionará para textos muito longos, mesmo com "Máximo de caracteres recuperados - dados XML" definido como ilimitado
Michael Møldrup
39

Veja como isso deve ser feito:

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*/

Retirado de http://ask.sqlservercentral.com/questions/3102/any-way-around-the-print-limit-of-nvarcharmax-in-s.html

Ben B
fonte
1
Ótima técnica! BTW, o artigo real que originou esta técnica foi de SQLServerCentral.com >>> sqlservercentral.com/scripts/Print/63240
Rob.Kachmar
2
Isso funcionou para mim, mas também dividiu um dos meus nomes de campo pela metade. Então, se eu usar esse método para PRINT (@string) e depois EXECUTE (@string), o EXECUTE falha.
Johnny Bones
1
Isso não funciona para mim, pois a função PRINT adiciona quebras de linha em lugares ruins e exigiria mais limpeza do que vale, mas esta é a solução mais próxima para o problema.
Randy Burden,
14

Encontrei esta pergunta e queria algo mais simples ... Experimente o seguinte:

SELECT [processing-instruction(x)]=@Script FOR XML PATH(''),TYPE
Edyn
fonte
5
Mais simples seria o SELECT CAST(@STMT AS XML)já afirmado em outro comentário. Produz exatamente a mesma saída e é menos complicado do que criar um procedimento armazenado para saída.
Felix Bayer
4
@Felix Embora seja muito mais simples, não funciona muito bem para SQL. A conversão em XML tenta converter o texto SQL em XML. Ele substituirá <,> e & por & lt ;, & gt; e & amp; e não vai lidar com caracteres não permitidos em XML. Além disso, se você tiver uma situação em que faz uma comparação de <e, em seguida,>, ele pensa que é um elemento e lança um erro de nó inválido.
Edyn
12

Este proc imprime corretamente o VARCHAR(MAX)parâmetro considerando o empacotamento:

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
Andrey Morozov
fonte
este procedimento está em conflito com caracteres Unicode. como lidar com utf8 por exemplo?
mostafa8026
na resposta ao comentário acima, isso pode ser feito alterando o tipo @script para nvarchar.
mostafa8026 de
8

Eu estava tentando usar a instrução print para depurar algum sql dinâmico, já que imagino que a maioria de vocês está usando print por motivos semelhantes.

Tentei algumas das soluções listadas e descobri que a solução de Kelsey funciona com pequenos ajustes (@sql é meu @script) nb LENGTH não é uma função válida:

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

Este código adiciona uma nova linha à saída, conforme comentado, mas para depurar isso não é um problema para mim.

A solução de Ben B é perfeita e é a mais elegante, embora para depuração haja muitas linhas de código, então escolho usar minha pequena modificação da de Kelsey. Pode valer a pena criar um sistema como procedimento armazenado em msdb para o código de Ben B, que pode ser reutilizado e chamado em uma linha?

O código do Alfoks não funciona, infelizmente, porque isso teria sido mais fácil.

Matthew Radford
fonte
Acabei de adicionar a solução de Ben B como um procedimento armazenado temporário. Mantém meus scripts um pouco mais limpos, mas concordo que são muitas linhas para depuração.
Zarepheth
4

Você pode usar isso

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
Marwan Almukh
fonte
4

Acabei de criar um SP a partir da ótima resposta de Ben :

/*
---------------------------------------------------------------------------------
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
Yovav
fonte
Maravilhoso, exatamente o que eu estava procurando!
Kooch
3
criar procedimento dbo.PrintMax @text nvarchar (max)
Como
início
    declara @i int, @newline nchar (2), @print varchar (max); 
    definir @nova linha = nchar (13) + nchar (10);
    selecione @i = charindex (@newline, @text);
    enquanto (@i> 0)
    início
        selecione @print = substring (@ text, 0, @ i);
        while (len (@print)> 8000)
        início
            substring de impressão (@ print, 0,8000);
            selecione @print = substring (@ print, 8000, len (@print));
        fim
        print @print;
        selecione @text = substring (@ text, @ i + 2, len (@text));
        selecione @i = charindex (@newline, @text);
    fim
    print @text;
fim
Adam Gering
fonte
2

Existe uma grande função chamada PrintMax escrita por Bennett Dill .

Aqui está uma versão ligeiramente modificada que usa procedimento armazenado temporário para evitar "poluição do esquema" (ideia de 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 Demo

EDITAR:

Usando CREATE OR ALTER, poderíamos evitar duas chamadas 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 <> demonstração de violino

Lukasz Szozda
fonte
2

Usa feeds de linha e espaços como um bom ponto de interrupção:

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 '   '
BickiBoy
fonte
Funcionou perfeitamente
Jolley71717
2

Ou simplesmente:

PRINT SUBSTRING(@SQL_InsertQuery, 1, 8000)
PRINT SUBSTRING(@SQL_InsertQuery, 8001, 16000)
Yovav
fonte
0

Aqui está outra versão. Este extrai cada substring para imprimir da string principal em vez de reduzir a string principal em 4000 em cada loop (o que pode criar muitas strings muito longas sob o capô - não tenho certeza).

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
redcalx
fonte
0

Isso deve funcionar corretamente - é apenas uma melhoria das respostas anteriores.

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
vinbhai4u
fonte
0

Se o código-fonte não tiver problemas com LF para ser substituído por CRLF, nenhuma depuração é necessária seguindo as saídas de códigos simples.

--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; 
Bill Bai
fonte
0

Minha versão PrintMax para evitar quebras de linha ruins na saída:


    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
Ercument Eskar
fonte