Jak wydrukować VARCHAR (MAX) za pomocą instrukcji Print?


108

Mam kod, który jest:

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)

Długość skryptu wynosi około 10 000 znaków, a ponieważ używam instrukcji print, która może pomieścić maksymalnie 8 000 znaków. Więc używam dwóch instrukcji print.

Problem polega na tym, że kiedy mam skrypt, który ma powiedzmy 18000 znaków, użyłem 3 instrukcji print.

Czy jest więc sposób, żebym mógł ustawić liczbę instrukcji print w zależności od długości skryptu?


1
Czy musisz używać, PRINTczy jesteś otwarty na inne alternatywy?
Martin Smith

Proponuję utworzyć (lub znaleźć i zagłosować) dla problemu na connect.microsoft.com/SQLServer/Feedback
jmoreno

Odpowiedzi:


23

Możesz zrobić WHILEpętlę na podstawie liczby długości skryptu podzielonej przez 8000.

NA PRZYKŁAD:

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

Jeśli spojrzysz na mój kod, używam również zmiennej @Pos, aby znaleźć podział wiersza i odpowiednio wydrukować. Więc jak mogłem tego użyć w twoim kodzie.
Piotr

@peter Możesz po prostu wziąć bieżący SUBSTRi spojrzeć tylko na część, z którą masz do czynienia w danym momencie i powtórzyć to lub jeśli wiesz, że za każdym razem nastąpi złamanie linii przed limitem 8k, a następnie po prostu wykonaj na WHILEpodstawie znalezienia linii przerwy.
Kelsey

@peter czy możesz zapętlić na podstawie podziałów linii? Na przykład poszukaj końca wiersza, jeśli zostanie znaleziony drukuj do końca wiersza, podciąg od końca wiersza do następnych 8k znaków, szukaj, drukuj, nowy podciąg itp.
Kelsey

1
Funkcja to LEN (), a nie LENGTH ()
shiggity

8
Kiedyś print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000))drukowałem swój skrypt.
Lukas Thum

217

Wiem, że to stare pytanie, ale nie ma tu mowy o tym, co zrobiłem.

Dla mnie zadziałało następujące.

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)

4
@gordy - Wydaje mi się więc, że ta metoda tak naprawdę nie działa w SSMS.
Jirka Hanika

1
Działa to dla mnie na SQL 2008 R2 z dodatkiem SP2 (10.50.1600) przy użyciu CAST () lub CONVERT () i na SQL 2008 SP2 (10.0.5500).

26
Widzę obcięcie po 16 002 znakach, wciąż dłuższe niż maxgdyby. 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
Typy danych ntext, text i image zostaną usunięte w przyszłej wersji Microsoft SQL Server. Unikaj używania tych typów danych w nowych pracach programistycznych i planuj modyfikowanie aplikacji, które obecnie z nich korzystają.
jumxozizi

5
Nie działa dla mnie w SQL Server Management Studio dla SQL Server 2014. Obcina po 16 000 znaków. Jak napisał Martin Smith.
Jana Weschenfelder

103

Poniższe obejście nie używa PRINTinstrukcji. Działa dobrze w połączeniu z SQL Server Management Studio.

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

Możesz kliknąć zwrócony kod XML, aby rozwinąć go we wbudowanej przeglądarce XML.

Wyświetlany rozmiar jest dość hojny po stronie klienta. Przejdź do, Tools/Options/Query Results/SQL Server/Results to Grid/XML dataaby w razie potrzeby dostosować.


11
+1. Ale ta metoda koduje znaki, które mają specjalne znaczenie w XML. Na przykład <jest zastępowany przez &lt;.
Iain Samuel McLean Elder

5
możesz pisać scenariusze bez <root>....SELECT CAST(@MyLongString AS XML)
czegoś

2
@aliyouhannaei - Tak i nie. Masz rację, że element główny nie jest koniecznie potrzebny. Ale bez sekcji CDATA Twoja metoda zaczyna mieć problemy z niektórymi napisami. Szczególnie te, które zawierają <. Jeśli nie są w formacie XML, zapytanie zwykle kończy się błędem. Jeśli są w formacie XML, łańcuch może zostać ponownie sformatowany w inny „równoważny” formularz XML.
Jirka Hanika,

8
@IainElder - To dobra uwaga i istnieje obejście tego problemu od Adama Machanica . Jest to: SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH(''). Ciąg zostanie zawinięty w PI o nazwie „x”, ale PI nie będzie zawijany w inny element (z powodu PATH('')).
Jirka Hanika,

To nie zadziała w przypadku bardzo długich tekstów, nawet przy ustawieniu „Maksymalna liczba pobranych znaków - dane XML” na nieograniczoną liczbę
Michael Møldrup

39

Oto, jak należy to zrobić:

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

Zaczerpnięte z http://ask.sqlservercentral.com/questions/3102/any-way-around-the-print-limit-of-nvarcharmax-in-s.html


1
Świetna technika! BTW, rzeczywisty artykuł, który zapoczątkował tę technikę, pochodzi z SQLServerCentral.com >>> sqlservercentral.com/scripts/Print/63240
Rob.Kachmar

2
To zadziałało dla mnie, ale również zmniejszyło jedną z moich nazw pól na pół. Tak więc, jeśli użyję tej metody do PRINT (@string), a następnie EXECUTE (@string), EXECUTE nie powiedzie się.
Johnny Bones

1
To nie działa dla mnie, ponieważ funkcja PRINT dodaje podziały wierszy w złych miejscach i wymagałaby więcej czyszczenia niż jest to warte, ale jest to najbliższe rozwiązanie problemu.
Randy Burden

14

Przyszedłem do tego pytania i chciałem czegoś prostszego ... Spróbuj wykonać następujące czynności:

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

5
Prostsze byłoby to, SELECT CAST(@STMT AS XML)co już stwierdzono w innym komentarzu. Daje dokładnie te same dane wyjściowe i jest rzeczywiście mniej skomplikowane niż tworzenie procedury składowanej dla danych wyjściowych.
Felix Bayer

4
@Felix To byłoby o wiele prostsze, ale nie działa w przypadku SQL. Rzutowanie na XML próbuje przekonwertować tekst SQL na XML. Zastąpi on <,> i & tagiem & lt ;, & gt; i & amp; i nie będzie obsługiwać znaków niedozwolonych w XML. Ponadto, jeśli masz sytuację, w której wykonujesz porównanie <, a następnie>, uważa, że ​​to element i zgłasza błąd nieprawidłowego węzła.
Edyn

12

Ten proc poprawnie wypisuje VARCHAR(MAX)parametr uwzględniający opakowanie:

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

ta procedura powoduje konflikt ze znakami Unicode. jak obsłużyć na przykład utf8?
mostafa8026

w odpowiedzi na powyższy komentarz można to zrobić zmieniając typ @script na nvarchar.
mostafa8026

8

Chciałem użyć instrukcji print do debugowania jakiegoś dynamicznego sql, ponieważ wyobrażam sobie, że większość z was używa print z podobnych powodów.

Wypróbowałem kilka z wymienionych rozwiązań i stwierdziłem, że rozwiązanie Kelsey działa z drobnymi poprawkami (@sql to mój @script). Nb LENGTH nie jest prawidłową funkcją:

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

Ten kod, zgodnie z komentarzem, dodaje nowy wiersz do danych wyjściowych, ale w przypadku debugowania nie stanowi to dla mnie problemu.

Rozwiązanie Bena B jest doskonałe i najbardziej eleganckie, chociaż do debugowania jest dużo linii kodu, więc zdecydowałem się użyć mojej niewielkiej modyfikacji Kelseya. Może warto byłoby stworzyć system podobny do procedury składowanej w msdb dla kodu Bena B, który można by ponownie wykorzystać i wywołać w jednej linii?

Kod Alfoksa niestety nie działa, ponieważ byłoby to łatwiejsze.


Właśnie dodałem rozwiązanie Bena B jako tymczasową procedurę składowaną. Utrzymuje moje skrypty nieco czystsze, ale zgadzam się, że jest to dużo wierszy do debugowania.
Zarepheth,

4

Możesz tego użyć

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

Właśnie stworzyłem SP z doskonałej odpowiedzi Bena :

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

Cudownie, właśnie tego szukałem!
kooch

3
procedura tworzenia dbo.PrintMax @text nvarchar (max)
tak jak
zaczynać
    zadeklaruj @i int, @newline nchar (2), @print varchar (max); 
    set @newline = nchar (13) + nchar (10);
    select @i = charindex (@newline, @text);
    podczas gdy (@i> 0)
    zaczynać
        select @print = podciąg (@ text, 0, @ i);
        podczas gdy (len (@print)> 8000)
        zaczynać
            print substring (@ print, 0,8000);
            select @print = substring (@ print, 8000, len (@print));
        koniec
        print @print;
        select @text = substring (@ text, @ i + 2, len (@text));
        select @i = charindex (@newline, @text);
    koniec
    print @text;
koniec

2

Istnieje świetna funkcja o nazwie PrintMax, napisana przez Bennetta Dilla .

Oto nieco zmodyfikowana wersja, która używa tymczasowej procedury składowanej, aby uniknąć „polution schematu” (pomysł z 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

EDYTOWAĆ:

Używając CREATE OR ALTERmożemy uniknąć dwóch wywołań 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

Używa znaków nowego wiersza i spacji jako dobrego punktu przerwania:

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

Działało bez zarzutu
Jolley71717

2

Lub po prostu:

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

0

Oto inna wersja. Ten wyodrębnia każdy podciąg do wydrukowania z głównego ciągu zamiast zmniejszać główny ciąg o 4000 w każdej pętli (co może spowodować powstanie wielu bardzo długich ciągów pod maską - nie jestem pewien).

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

To powinno działać poprawnie, to tylko ulepszenie poprzednich odpowiedzi.

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

Jeśli kod źródłowy nie będzie miał problemów z zastąpieniem LF przez CRLF, nie jest wymagane debugowanie przez wykonanie prostych kodów wyjściowych.

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

Moja wersja PrintMax do zapobiegania złym podziałom wierszy na wyjściu:


    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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.