¿Cómo imprimir VARCHAR (MAX) usando Print Statement?

108

Tengo un código que es:

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)

La longitud de la secuencia de comandos es de alrededor de 10,000 caracteres y, dado que estoy usando una declaración impresa, solo puede contener un máximo de 8000. Por lo tanto, estoy usando dos declaraciones impresas.

El problema es que cuando tengo un script que tiene, digamos, 18000 caracteres, solía usar 3 declaraciones de impresión.

Entonces, ¿hay alguna manera de establecer el número de declaraciones de impresión en función de la longitud del script?

pedro
fuente
1
¿Tienes que usar PRINTo estás abierto a otras alternativas?
Martin Smith
Sugeriría crear (o buscar y votar) un problema en connect.microsoft.com/SQLServer/Feedback
jmoreno

Respuestas:

23

Puede hacer un WHILEbucle basado en el recuento de la longitud de su script dividido por 8000.

P.EJ:

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
fuente
Si miras mi código, también estoy usando la variable @Pos para encontrar el salto de línea e imprimir en consecuencia. Entonces, ¿cómo podría usar eso en tu código?
peter
@peter Puede tomar la corriente SUBSTRy mirar solo la parte con la que está tratando en ese momento e iterar sobre eso o si sabe que habrá un salto de línea antes del límite de 8k cada vez, simplemente haga lo WHILEbasado en encontrar la línea rompe.
Kelsey
@peter ¿puedes hacer un bucle basado en los saltos de línea? Por ejemplo, busque el salto de línea, si lo encuentra, imprima hasta el salto de línea, subcadena desde el salto de línea hasta los siguientes 8k caracteres, busque, imprima, nueva substr, etc.
Kelsey
1
La función es LEN () no LENGTH ()
shiggity
8
Solía print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000))imprimir mi guión.
Lukas Thum
217

Sé que es una vieja pregunta, pero lo que hice no se menciona aquí.

Para mí funcionó lo siguiente.

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)
alfoks
fuente
4
@gordy: me parece que este método realmente no funciona en SSMS.
Jirka Hanika
1
Esto me funciona en SQL 2008 R2 SP2 (10.50.1600) usando CAST () o CONVERT (), y en SQL 2008 SP2 (10.0.5500).
26
Veo truncamiento después de 16,002 caracteres, aún más largo que maxaunque. 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
Los tipos de datos de texto, texto e imagen se eliminarán en una versión futura de Microsoft SQL Server. Evite el uso de estos tipos de datos en nuevos trabajos de desarrollo y planee modificar las aplicaciones que los usan actualmente.
jumxozizi
5
No funcionó para mí en SQL Server Management Studio para SQL Server 2014. Corta después de 16.000 caracteres. Según lo escrito por Martin Smith.
Jana Weschenfelder
103

La siguiente solución alternativa no utiliza la PRINTdeclaración. Funciona bien en combinación con SQL Server Management Studio.

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

Puede hacer clic en el XML devuelto para expandirlo en el visor XML integrado.

Hay un límite bastante generoso del lado del cliente en el tamaño mostrado. Vaya a Tools/Options/Query Results/SQL Server/Results to Grid/XML datapara ajustarlo si es necesario.

Jirka Hanika
fuente
11
+1. Pero este método codifica caracteres que tienen un significado especial en XML. Por ejemplo, <se reemplaza por &lt;.
Iain Samuel McLean Elder
5
puedes escribir un guión sin me <root>....gusta:SELECT CAST(@MyLongString AS XML)
ali youhannaei
2
@aliyouhannaei - Sí y no. Tiene razón en que el elemento raíz no es estrictamente necesario. Pero, sin la sección CDATA, su método comienza a tener problemas con algunas cadenas. Especialmente aquellos que contienen <. Si no son XML, la consulta normalmente generará un error. Si son XML, la cadena puede terminar reformateada en otro formato XML "equivalente".
Jirka Hanika
8
@IainElder - Ese es un buen punto y hay una solución alternativa de Adam Machanic . Es esta: SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH(''). La cadena estará envuelta en un PI llamado "x", pero el PI no estará envuelto en otro elemento (debido a PATH('')).
Jirka Hanika
Esto no funcionará para textos muy largos, incluso con "Caracteres máximos recuperados - Datos XML" configurados como ilimitados
Michael Møldrup
39

Así es como se debe hacer esto:

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

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

Ben B
fuente
1
¡Gran técnica! Por cierto, el artículo real que originó esta técnica fue de SQLServerCentral.com >>> sqlservercentral.com/scripts/Print/63240
Rob.Kachmar
2
Esto funcionó para mí, pero también cortó uno de mis nombres de campo por la mitad. Entonces, si utilicé este método para PRINT (@string) y luego EXECUTE (@string), EXECUTE falla.
Johnny Bones
1
Esto no funciona para mí ya que la función PRINT agrega saltos de línea en lugares incorrectos y requeriría más limpieza de la que vale, pero esta es la solución más cercana al problema.
Randy Burden
14

Encontré esta pregunta y quería algo más simple ... Intente lo siguiente:

SELECT [processing-instruction(x)]=@Script FOR XML PATH(''),TYPE
Edyn
fuente
5
Más simple sería SELECT CAST(@STMT AS XML)como ya se dijo en otro comentario. Produce exactamente la misma salida y, de hecho, es menos complicado que crear un procedimiento almacenado para la salida.
Felix Bayer
4
@Felix Si bien eso sería mucho más simple, no funciona del todo para SQL. La conversión a XML intenta convertir el texto SQL a XML. Reemplazará <,> y & por & lt ;, & gt; y & amp; y no manejará caracteres no permitidos en XML. Además, si tiene una situación en la que hace una comparación de <y luego>, cree que es un elemento y arroja un error de nodo no válido.
Edyn
12

Este proceso imprime correctamente el VARCHAR(MAX)parámetro considerando el ajuste:

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
fuente
este procedimiento tiene un conflicto con los caracteres Unicode. cómo manejar utf8 por ejemplo?
mostafa8026
en la respuesta al comentario anterior, se puede hacer cambiando el tipo @script a nvarchar.
mostafa8026
8

Estaba buscando usar la declaración de impresión para depurar algún SQL dinámico, ya que imagino que la mayoría de ustedes está usando la impresión por razones similares.

Probé algunas de las soluciones enumeradas y descubrí que la solución de Kelsey funciona con ajustes menores (@sql es mi @script) nb LENGTH no es una función 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, como se comentó, agrega una nueva línea a la salida, pero para depurar esto no es un problema para mí.

La solución de Ben B es perfecta y es la más elegante, aunque para la depuración hay muchas líneas de código, así que elijo usar mi pequeña modificación de Kelsey. ¿Podría valer la pena crear un sistema como un procedimiento almacenado en msdb para el código de Ben B que podría reutilizarse y llamarse en una línea?

El código de Alfoks no funciona desafortunadamente porque eso hubiera sido más fácil.

Matthew Radford
fuente
Acabo de agregar la solución de Ben B como un procedimiento almacenado temporal. Mantiene mis scripts un poco más limpios, pero estoy de acuerdo en que son muchas líneas para depurar.
Zarepheth
4

Puedes usar esto

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
fuente
4

Acabo de crear un SP a partir de la gran respuesta 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
fuente
¡Maravilloso, justo lo que estaba buscando!
kooch
3
crear procedimiento dbo.PrintMax @text nvarchar (max)
como
empezar
    declare @i int, @newline nchar (2), @print varchar (max); 
    establecer @newline = nchar (13) + nchar (10);
    seleccione @i = charindex (@newline, @text);
    mientras (@i> 0)
    empezar
        seleccione @print = substring (@ text, 0, @ i);
        while (len (@print)> 8000)
        empezar
            print subcadena (@ print, 0,8000);
            seleccione @print = substring (@ print, 8000, len (@print));
        final
        print @print;
        seleccionar @texto = subcadena (@ texto, @ i + 2, len (@texto));
        seleccione @i = charindex (@newline, @text);
    final
    imprimir @texto;
final
Adam Gering
fuente
2

Hay una gran función llamada PrintMax escrita por Bennett Dill .

Aquí hay una versión ligeramente modificada que usa un procedimiento almacenado temporal para evitar la "contaminación del esquema" (idea 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;');

Demostración de DBFiddle

EDITAR:

Usando CREATE OR ALTERpodríamos evitar dos llamadas 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 <> demostración de violín

Lukasz Szozda
fuente
2

Utiliza saltos de línea y espacios como un buen punto de interrupción:

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
fuente
Funcionó
2

O simplemente:

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

Aquí tienes otra versión. Este extrae cada subcadena para imprimir de la cadena principal en lugar de reducir la cadena principal en 4000 en cada bucle (lo que podría crear muchas cadenas muy largas debajo del capó, no estoy seguro).

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
fuente
0

Esto debería funcionar correctamente, esto es solo una mejora de las respuestas 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
fuente
0

Si el código fuente no tendrá problemas con LF para ser reemplazado por CRLF, no se requiere depuración siguiendo las salidas 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
fuente
0

Mi versión PrintMax para evitar saltos de línea defectuosos en la salida:


    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
fuente