Come stampare VARCHAR (MAX) utilizzando l'istruzione Print?


108

Ho un codice che è:

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 lunghezza dello script è di circa 10.000 caratteri e poiché sto usando l'istruzione print che può contenere solo un massimo di 8000. Quindi sto usando due istruzioni print.

Il problema è quando ho uno script che è di dire 18000 caratteri, allora usavo 3 istruzioni di stampa.

Quindi c'è un modo per impostare il numero di istruzioni di stampa a seconda della lunghezza dello script?


1
Devi usare PRINTo sei aperto ad altre alternative?
Martin Smith

Suggerirei di creare (o trovare e votare) per un problema su connect.microsoft.com/SQLServer/Feedback
jmoreno

Risposte:


23

Potresti fare un WHILEciclo basato sul conteggio della lunghezza dello script diviso per 8000.

PER ESEMPIO:

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

Se guardi il mio codice sto anche usando la variabile @Pos per trovare l'interruzione di riga e stampare di conseguenza. Quindi come potrei usarlo nel tuo codice.
peter

@peter Puoi semplicemente prendere la corrente SUBSTRe guardare solo la parte con cui hai a che fare in quel momento e iterare su quella o se sai che ogni volta ci sarà un'interruzione di riga prima del limite di 8k, allora fai semplicemente la WHILElinea di ricerca pause.
Kelsey

@peter puoi ripetere il ciclo in base alle interruzioni di riga? Ad esempio, cerca interruzione di riga, se trovato stampa fino all'interruzione di riga, substr dall'interruzione di riga ai successivi 8k caratteri, cerca, stampa, nuovo substr, ecc.?
Kelsey

1
La funzione è LEN () non LENGTH ()
shiggity

8
Ho usato print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000))per stampare il mio script.
Lukas Thum

217

So che è una vecchia domanda, ma quello che ho fatto non è menzionato qui.

Per me ha funzionato quanto segue.

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)

4
@gordy - Quindi mi sembra che questo metodo non funzioni davvero in SSMS.
Jirka Hanika

1
Questo funziona per me su SQL 2008 R2 SP2 (10.50.1600) utilizzando CAST () o CONVERT () e su SQL 2008 SP2 (10.0.5500).

26
Vedo un troncamento dopo 16.002 caratteri, ancora più lungo di maxquanto non lo sia. 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
I tipi di dati ntext, text e image verranno rimossi in una versione futura di Microsoft SQL Server. Evita di utilizzare questi tipi di dati in un nuovo lavoro di sviluppo e pianifica di modificare le applicazioni che li utilizzano attualmente.
jumxozizi

5
Non ha funzionato per me in SQL Server Management Studio per SQL Server 2014. Taglia dopo 16.000 caratteri. Come scritto da Martin Smith.
Jana Weschenfelder

103

La seguente soluzione alternativa non utilizza l' PRINTistruzione. Funziona bene in combinazione con SQL Server Management Studio.

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

È possibile fare clic sull'XML restituito per espanderlo nel visualizzatore XML integrato.

Esiste un limite lato client piuttosto generoso per le dimensioni visualizzate. Vai a Tools/Options/Query Results/SQL Server/Results to Grid/XML dataper regolarlo se necessario.


11
+1. Ma questo metodo codifica caratteri che hanno un significato speciale in XML. Ad esempio, <viene sostituito con &lt;.
Iain Samuel McLean Elder

5
puoi scrivere la sceneggiatura senza mi <root>....piace:SELECT CAST(@MyLongString AS XML)
ali youhannaei

2
@aliyouhannaei - Sì e no. Hai ragione che l'elemento radice non è strettamente necessario. Ma, senza la sezione CDATA, il tuo metodo inizia ad avere problemi con alcune stringhe. Soprattutto quelli che contengono <. Se non sono XML, la query di solito viene visualizzata in errore. Se sono XML, la stringa potrebbe finire per essere riformattata in un altro formato XML "equivalente".
Jirka Hanika

8
@ IainElder - Questo è un buon punto e c'è una soluzione alternativa per questo da Adam Machanic . E 'questo: SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH(''). La stringa verrà racchiusa in un PI chiamato "x", ma il PI non verrà racchiuso in un altro elemento (a causa di PATH('')).
Jirka Hanika

Questo non funzionerà per testi molto lunghi, anche con "Numero massimo di caratteri recuperati - Dati XML" impostato su illimitato
Michael Møldrup

39

Ecco come dovrebbe essere fatto:

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

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


1
Ottima tecnica! A proposito, l'articolo effettivo che ha originato questa tecnica era da SQLServerCentral.com >>> sqlservercentral.com/scripts/Print/63240
Rob.Kachmar

2
Questo ha funzionato per me, ma ha anche tagliato a metà uno dei miei nomi di campo. Quindi, se ho usato questo metodo per PRINT (@string) e poi EXECUTE (@string), EXECUTE fallisce.
Johnny Bones

1
Questo non funziona per me in quanto la funzione PRINT aggiunge interruzioni di riga in punti cattivi e richiederebbe più pulizia di quanto valga, ma questa è la soluzione più vicina al problema.
Randy Burden

14

Mi sono imbattuto in questa domanda e volevo qualcosa di più semplice ... Prova quanto segue:

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

5
Più semplice sarebbe SELECT CAST(@STMT AS XML)come già affermato in un altro commento. Produce esattamente lo stesso output ed è effettivamente meno complicato rispetto alla creazione di una stored procedure per l'output.
Felix Bayer

4
@Felix Anche se sarebbe molto più semplice, non funziona per SQL. La trasmissione in XML tenta di convertire il testo SQL in XML. Sostituirà <,> e & con & lt ;, & gt; e & amp; e non gestirà i caratteri non consentiti in XML. Inoltre, se hai una situazione in cui fai un confronto di <e poi>, pensa che sia un elemento e genera un errore di nodo non valido.
Edyn

12

Questo proc stampa correttamente il VARCHAR(MAX)parametro considerando il wrapping:

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

questa procedura ha un conflitto con i caratteri Unicode. come gestire utf8 per esempio?
mostafa8026

nella risposta al commento sopra, può essere fatto cambiando il tipo @script in nvarchar.
mostafa8026

8

Stavo cercando di utilizzare l'istruzione print per eseguire il debug di alcuni sql dinamici poiché immagino che la maggior parte di voi stia usando print per ragioni simili.

Ho provato alcune delle soluzioni elencate e ho scoperto che la soluzione di Kelsey funziona con tweek minori (@sql è il mio @script) nb LENGTH non è una funzione valida:

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

Questo codice come commentato aggiunge una nuova riga nell'output, ma per il debug questo non è un problema per me.

La soluzione di Ben B è perfetta ed è la più elegante, anche se per il debug ci sono molte righe di codice, quindi scelgo di usare la mia leggera modifica di Kelsey. Potrebbe valere la pena creare un sistema come la stored procedure in msdb per il codice di Ben B che potrebbe essere riutilizzato e chiamato in una riga?

Il codice di Alfoks non funziona purtroppo perché sarebbe stato più facile.


Ho appena aggiunto la soluzione di Ben B come stored procedure temporanea. Mantiene i miei script un po 'più puliti, ma sono d'accordo sul fatto che ci siano molte righe per il debug.
Zarepheth

4

Puoi usarlo

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

Ho appena creato un SP dalla grande risposta di 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

Meraviglioso, proprio quello che stavo cercando!
kooch

3
creare procedura dbo.PrintMax @text nvarchar (max)
come
inizio
    dichiarare @i int, @newline nchar (2), @print varchar (max); 
    impostare @newline = nchar (13) + nchar (10);
    seleziona @i = charindex (@newline, @text);
    while (@i> 0)
    inizio
        selezionare @print = sottostringa (@ text, 0, @ i);
        while (len (@print)> 8000)
        inizio
            stampa sottostringa (@ print, 0,8000);
            selezionare @print = substring (@ print, 8000, len (@print));
        fine
        print @print;
        selezionare @testo = sottostringa (@ testo, @ i + 2, len (@testo));
        seleziona @i = charindex (@newline, @text);
    fine
    print @text;
fine

2

C'è una grande funzione chiamata PrintMax scritta da Bennett Dill .

Ecco una versione leggermente modificata che utilizza una stored procedure temporanea per evitare "inquinamento dello schema" (idea da 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

MODIFICARE:

Utilizzando CREATE OR ALTERpotremmo evitare due chiamate 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

Utilizza gli avanzamenti riga e gli spazi come buon punto di interruzione:

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

Ha funzionato perfettamente
Jolley71717

2

O semplicemente:

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

0

Ecco un'altra versione. Questo estrae ogni sottostringa da stampare dalla stringa principale invece di ridurre la stringa principale di 4000 su ogni loop (il che potrebbe creare molte stringhe molto lunghe sotto il cofano - non sono sicuro).

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

Questo dovrebbe funzionare correttamente, è solo un miglioramento delle risposte precedenti.

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

Se il codice sorgente non avrà problemi con LF da sostituire da CRLF, non è richiesto alcun debug seguendo semplici output di codici.

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

La mia versione PrintMax per evitare interruzioni di riga errate in uscita:


    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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.