157

I have a code which is:

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)

The length of the Script is around 10,000 Characters and Since I am using print Statement which can hold only max of 8000. So I am using two print statements.

The problem is when I have a script which is of say 18000 characters then I used to use 3 print statements.

So Is there a way that I could set the number of print statements depending on the length of the script?

3
  • 1
    Do you have to use PRINT or are you open to other alternatives? Commented Oct 21, 2011 at 14:13
  • I'd suggest creating (or finding and voting) for an issue on connect.microsoft.com/SQLServer/Feedback Commented Feb 24, 2017 at 13:30
  • 7
    10 years later and these sort of hacks are still needed. Sad, really Commented Jan 6, 2022 at 18:06

19 Answers 19

291

I know it's an old question, but what I did is not mentioned here.

For me the following worked (for up to 16k chars)

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)

If you have more than 16k chars you can combine with @Yovav's answer like this (64k should be enough for anyone ;)

    print cast( substring(@info, 1, 16000) as ntext )
    print cast( substring(@info, 16001, 16000) as ntext )
    print cast( substring(@info, 32001, 16000) as ntext )
    print cast( substring(@info, 48001, 16000) as ntext )
Sign up to request clarification or add additional context in comments.

13 Comments

@gordy - So it seems to me that this method does not really work in SSMS.
This works for me on SQL 2008 R2 SP2 (10.50.1600) using either CAST() or CONVERT(), and on SQL 2008 SP2 (10.0.5500).
I see truncation after 16,002 characters, still longer than max though. DECLARE @info NVARCHAR(MAX) = 'A';SET @info = REPLICATE(@info, 16000) + 'BC This is not printed';PRINT @info;PRINT CAST(@info AS NTEXT);
ntext , text, and image data types will be removed in a future version of Microsoft SQL Server. Avoid using these data types in new development work, and plan to modify applications that currently use them.
Didn't work for me in SQL Server Management Studio for SQL Server 2014. It cuts after 16.000 characters. As written by Martin Smith.
|
139

The following workaround does not use the PRINT statement. It works well in combination with the SQL Server Management Studio.

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

You can click on the returned XML to expand it in the built-in XML viewer.

There is a pretty generous client side limit on the displayed size. Go to Tools/Options/Query Results/SQL Server/Results to Grid/XML data to adjust it if needed.

8 Comments

+1. But this method encodes characters that have a special meaning in XML. For example, < is replaced with &lt;.
you can write script without <root>.... like: SELECT CAST(@MyLongString AS XML)
@aliyouhannaei - Yes and no. You are right that the root element isn't strictly necessary. But, without the CDATA section, your method starts having trouble with some strings. Especially those that contain <. If they aren't XML, the query will usually error out. If they are XML, the string may end up reformatted into another "equivalent" XML form.
@IainElder - That's a good point and there's a workaround for it from Adam Machanic. It's this: SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH(''). The string will be wrapped in a PI called "x", but the PI won't be wrapped in another element (because of PATH('')).
This won't work for very long texts, even with "Maximum Characters Retrieved - XML data" set to unlimited
|
46

Here is how this should be done:

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

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

4 Comments

Great technique! BTW, the actual article which originated this technique was from SQLServerCentral.com >>> sqlservercentral.com/scripts/Print/63240
This worked for me, but it also chopped one of my field names in half. So, if I used this method to PRINT (@string) and then EXECUTE (@string), the EXECUTE fails.
This doesn't work for me as the PRINT function adds line breaks in bad places and would require more cleanup than it's worth but this is the closest solution to the problem.
Finally a script that works !! I know it's +10 years since your comment but well done and thank you !
30

You could do a WHILE loop based on the count on your script length divided by 8000.

EG:

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

10 Comments

If you look at my code I am also using the @Pos variable to find the line break and print accordingly. So How could I use that in your code.
@peter You can just take the current SUBSTR and look at only the part you are dealing with at the time and iterate on that or if you know that there will be a line break before the 8k limit each time then just do the WHILE based on finding line breaks.
The function is LEN() not LENGTH()
I used print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000)) to print my script.
I inserted the print statement before the counter increment, print substring(@script, (@Counter * 8000) + 1, 8000)
|
17

Came across this question and wanted something more simple... Try the following:

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

2 Comments

More simple would be SELECT CAST(@STMT AS XML) as already stated in another comment. Produces exactly the same output and is indeed less complicated than creating a stored procedure for output.
@Felix While that would be much simpler, it doesn't quite work for SQL. Casting to XML tries to convert the SQL text to XML. It will replace <, >, and & with &lt;, &gt; and &amp; and it won't handle chars not allowed in XML. Additionally, if you have a situation where you do a comparison of < and then >, it thinks that's an element and throws an invalid node error.
15

I just created a SP out of Ben's great answer:

/*
---------------------------------------------------------------------------------
PURPOSE   : Print a string without the limitation of 4000 or 8000 characters.
https://stackoverflow.com/questions/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

1 Comment

Wonderful, just what i was looking for!
13

This proc correctly prints out VARCHAR(MAX) parameter considering 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

2 Comments

this procedure has a conflict with Unicode characters. how to handle utf8 for example?
in the reply to above comment, it can be done by changing the @script type to nvarchar.
9

I was looking to use the print statement to debug some dynamic sql as I imagin most of you are using print for simliar reasons.

I tried a few of the solutions listed and found that Kelsey's solution works with minor tweeks (@sql is my @script) n.b. LENGTH isn't a valid function:

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

This code does as commented add a new line into the output, but for debugging this isn't a problem for me.

Ben B's solution is perfect and is the most elegent, although for debugging is a lot of lines of code so I choose to use my slight modification of Kelsey's. It might be worth creating a system like stored procedure in msdb for Ben B's code which could be reused and called in one line?

Alfoks' code doesn't work unfortunately because that would have been easier.

1 Comment

I just added Ben B's solution as a temporary stored procedure. Keeps my scripts a bit cleaner, but I agree that it's a lot of lines for debugging.
7

Or simply:

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

1 Comment

You could extend Yavav's suggestion here and simply use many 8000 char chunks... covering your worst case scenario... So if @SQL_InsertQuery was actually short just the first few lines print, the remaining ones just print an empty string
6

You can use this

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

Comments

4

There is great function called PrintMax written by Bennett Dill.

Here is slightly modified version that uses temp stored procedure to avoid "schema polution"(idea from 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

EDIT:

Using CREATE OR ALTER we could avoid two EXEC calls:

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

Comments

3
create procedure dbo.PrintMax @text nvarchar(max)
as
begin
    declare @i int, @newline nchar(2), @print varchar(max); 
    set @newline = nchar(13) + nchar(10);
    select @i = charindex(@newline, @text);
    while (@i > 0)
    begin
        select @print = substring(@text,0,@i);
        while (len(@print) > 8000)
        begin
            print substring(@print,0,8000);
            select @print = substring(@print,8000,len(@print));
        end
        print @print;
        select @text = substring(@text,@i+2,len(@text));
        select @i = charindex(@newline, @text);
    end
    print @text;
end

Comments

2

Uses Line Feeds and spaces as a good break point:

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

1 Comment

Worked flawlessly
1

My PrintMax version for prevent bad line breaks on output:


    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

Comments

0

Here's another version. This one extracts each substring to print from the main string instead of taking reducing the main string by 4000 on each loop (which might create a lot of very long strings under the hood - not sure).

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

Comments

0

This should work properly this is just an improvement of previous answers.

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

Comments

0

If the source code will not have issues with LF to be replaced by CRLF, No debugging is required by following simple codes outputs.

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

Comments

0

If someone interested I've ended up as generating a text file with powershell, executing scalar code:

$dbconn = "Data Source=sqlserver;" + "Initial Catalog=DatabaseName;" + "User Id=sa;Password=pass;"
$conn = New-Object System.Data.SqlClient.SqlConnection($dbconn)
$conn.Open()

$cmd = New-Object System.Data.SqlClient.SqlCommand("
set nocount on

DECLARE @sql nvarchar(max) = ''

SELECT 
    @sql += CHAR(13) + CHAR(10) + md.definition + CHAR(13) + CHAR(10) + 'GO'
FROM sys.objects AS obj
join sys.sql_modules AS md on md.object_id = obj.object_id
join sys.schemas AS sch on sch.schema_id = obj.schema_id
where obj.type = 'TR'


select @sql
", $conn)
$data = [string]$cmd.ExecuteScalar()


$conn.Close()

$data | Out-File -FilePath "C:\Users\Alexandru\Desktop\bigstring.txt"

This script it's for getting a big string with all the triggers from the DB.

Comments

0

I read through the above and tried my own simplified version that outputs the procedure text with the correct vertical spacing. Variable declares

DECLARE @tp     as varchar(100)
DECLARE @def    as nvarchar(MAX)
DECLARE @prt1   as nvarchar(4000)
DECLARE @prt2   as nvarchar(MAX)
DECLARE @pos1   as int
DECLARE @pos2   as int

in my implementation I am looking to output a list of procedures listed in a prior query and held in a temp table. I then loop through them setting the variable @tp to the procedure I want to output. I set the variable @def to the procedure definition

    SET @def = (SELECT  CAST(m.definition as nvarchar(MAX))
                FROM    sys.objects o
                            LEFT JOIN sys.sql_modules m
                            on m.object_id = o.object_id
                WHERE   o.name = @tp) 

then I use a simple "carving loop" to size down the output to be compatible with the SSMS message output window and print statement

    WHILE (Len(@def) > 4000)
      BEGIN
        SET @pos1 = CHARINDEX(CHAR(13), @Def,3600)
        SET @prt1 = SUBSTRING(@def,1,@pos1-1)
        SET @pos2 = LEN(@Def)
        SET @prt2 = SUBSTRING(@def, @pos1+2, @pos2-@pos1)
        SET @def = @prt2
        print @prt1 
      END
    Print @def

once it is below the 4k character limit I simply output what is remaining. We bypass the intervening CR/LF as that is supplied via the Print command. It is nothing revolutionary but has the benefit of being straight-forward and quite easy to read.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.