如何使用Print Statement打印VARCHAR(MAX)?


108

我有一个代码是:

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)

脚本的长度大约为10,000个字符,并且由于我使用的print语句最多只能容纳8000个字符。因此,我使用的是两个print语句。

问题是当我有一个脚本(例如18000个字符)时,我曾经使用3条打印语句。

那么,有没有一种方法可以根据脚本的长度来设置打印语句的数量?


1
您必须使用PRINT还是可以接受其他替代方案?
马丁·史密斯,

Answers:


23

您可以WHILE根据脚本长度除以8000 来进行循环。

例如:

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

如果您查看我的代码,我还将使用@Pos变量来查找换行符并进行相应的打印。那么我该如何在您的代码中使用它呢?
彼得

@peter您可以只获取当前电流SUBSTR,然后仅查看当时要处理的部分,然后对其进行迭代,或者如果您知道每次在8k限制之前都会有换行符,则只需WHILE根据查找线进行休息。
Kelsey

@peter您可以根据换行符循环吗?例如寻找换行符,如果发现可以打印到换行符,从换行符转换到下一个8k字符,搜索,打印,新的换行符等?
Kelsey

1
该函数是LEN()而不是LENGTH()
shiggity

8
我曾经print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000))打印我的脚本。
卢卡斯·图姆

217

我知道这是一个老问题,但是这里没有提及我所做的事情。

对我来说,以下工作。

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)

4
@gordy-因此在我看来,此方法在SSMS中实际上不起作用。
Jirka Hanika 2013年

1
这对我使用CAST()或CONVERT()的SQL 2008 R2 SP2(10.50.1600)和SQL 2008 SP2(10.0.5500)都适用。

26
我看到在16002个字符之后会被截断,但仍然更长maxDECLARE @info NVARCHAR(MAX) = 'A';SET @info = REPLICATE(@info, 16000) + 'BC This is not printed';PRINT @info;PRINT CAST(@info AS NTEXT);
马丁·史密斯

6
在将来的Microsoft SQL Server版本中,将删除ntext,text和image数据类型。避免在新的开发工作中使用这些数据类型,并计划修改当前使用它们的应用程序。
jumxozizi

5
在适用于SQL Server 2014的SQL Server Management Studio中对我不起作用。它将削减16.000个字符。正如马丁·史密斯(Martin Smith)所写。
Jana Weschenfelder

103

以下变通办法不使用该PRINT语句。与SQL Server Management Studio结合使用时效果很好。

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

您可以单击返回的XML,以在内置XML查看器中将其展开。

客户端对显示的大小有很大的限制。Tools/Options/Query Results/SQL Server/Results to Grid/XML data如果需要,请进行调整。


11
+1。但是,此方法对在XML中具有特殊含义的字符进行编码。例如,<被替换为&lt;
伊恩·塞缪尔·麦克莱恩

5
您可以编写脚本而无需<root>....像:SELECT CAST(@MyLongString AS XML)
ali youhannaei

2
@aliyouhannaei-是和否。您认为根元素不是严格必要的,这是正确的。但是,如果没有CDATA部分,您的方法就会遇到一些字符串问题。特别是那些包含<。如果它们不是XML,则查询通常会出错。如果它们是XML,则字符串最终可能会重新格式化为另一种“等效” XML格式。
Jirka Hanika 2015年

8
@IainElder-很好,Adam Machanic提供了一种解决方法。就是这个:SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH('')。字符串将被包装在名为“ x”的PI中,但PI不会被包装在另一个元素中(由于PATH(''))。
Jirka Hanika 2015年

即使“已检索的最大字符数
-XML

39

这是应该怎么做:

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

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


1
很棒的技术!顺便说一句,起源于此技术的实际文章来自SQLServerCentral.com >>> sqlservercentral.com/scripts/Print/63240
Rob.Kachmar,2014年

2
这对我有用,但也将我的一个字段名称切成了两半。因此,如果我使用此方法先打印(@string),然后执行(@string),则执行失败。
约翰尼·博恩斯

1
这对我来说不起作用,因为PRINT函数会在不好的地方添加换行符,并且需要进行的清理工作超出其应有的价值,但这是最接近该问题的解决方案。
兰迪·伯顿

14

遇到了这个问题,想要更简单的方法...请尝试以下操作:

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

5
SELECT CAST(@STMT AS XML)如另一条评论中所述,更为简单。产生完全相同的输出,并且确实比为输出创建存储过程要简单。
菲利克斯·拜耳

4
@Felix虽然那会简单得多,但对于SQL来说却不太有效。转换为XML会尝试将SQL文本转换为XML。它将<,>和&替换为&lt;,&gt; 和&amp; 并且它将无法处理XML中不允许的字符。此外,如果遇到先比较<然后比较>的情况,它会认为这是一个元素,并抛出无效的节点错误。
Edyn 2015年

12

VARCHAR(MAX)考虑到换行,此proc可以正确打印出参数:

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

此过程与Unicode字符冲突。例如如何处理utf8?
mostafa8026

在上述评论的回复中,可以通过将@script类型更改为nvarchar来完成。
mostafa8026

8

我一直想使用print语句来调试一些动态sql,因为我想大多数人出于相似的原因而使用print。

我尝试了列出的一些解决方案,发现Kelsey的解决方案在较小的tweek内有效(@sql是我的@script)nb LENGTH不是有效的函数:

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

这段代码确实按照注释的方式在输出中添加了新行,但是对于调试而言,这对我来说不是问题。

Ben B的解决方案是完美的,并且是最完善的,尽管用于调试的代码行很多,所以我选择使用对Kelsey的略微修改。为Ben B的代码在msdb中创建一个像存储过程这样的系统可能值得重复使用并在一行中调用它?

不幸的是,Alfoks的代码无法正常工作,因为那样会更容易。


我刚刚添加了Ben B的解决方案作为临时存储过程。使我的脚本保持整洁,但是我同意调试的代码很多。
Zarepheth '17


4

我只是根据Ben的出色回答创建了一个SP :

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

太好了,正是我想要的!
kooch

3
创建过程dbo.PrintMax @text nvarchar(max)
如
开始
    声明@i int,@newline nchar(2),@print varchar(max); 
    设置@newline = nchar(13)+ nchar(10);
    选择@i = charindex(@newline,@text);
    而(@i> 0)
    开始
        选择@print = substring(@ text,0,@ i);
        而(len(@print)> 8000)
        开始
            打印子字符串(@ print,0,8000);
            选择@print = substring(@ print,8000,len(@print));
        结束
        打印@打印;
        选择@text = substring(@ text,@ i + 2,len(@text));
        选择@i = charindex(@newline,@text);
    结束
    打印@text;
结束

2

Bennett Dill编写了一个称为PrintMax的强大功能。

这是使用临时存储过程以避免“模式污染”的略微修改版本(来自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演示

编辑:

使用CREATE OR ALTER我们可以避免两个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演示


2

使用换行符和空格作为良好的断点:

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

完美无缺地工作
Jolley71717 '11

2

或者简单地:

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

0

这是另一个版本。这是从主字符串中提取每个要打印的子字符串,而不是在每个循环中将主字符串减少4000(这可能在引擎盖下创建很多非常长的字符串-不确定)。

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

这应该可以正常工作,这只是先前答案的改进。

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

如果源代码不会出现由LF替换为LF的问题,则遵循以下简单代码输出就无需调试。

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

我的PrintMax版本可防止输出出现断行:


    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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.