【问题标题】:How to print VARCHAR(MAX) using Print Statement?如何使用打印语句打印 VARCHAR(MAX)?
【发布时间】:2011-10-21 14:00:35
【问题描述】:

我有一个代码是:

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 个打印语句。

那么有没有办法可以根据脚本的长度设置打印语句的数量?

【问题讨论】:

  • 您必须使用PRINT 还是愿意接受其他替代方案?
  • 我建议在 connect.microsoft.com/SQLServer/Feedback 上为问题创建(或查找和投票)
  • 10 年后,仍然需要这些技巧。伤心,真的

标签: sql sql-server sql-server-2005 tsql sql-server-2008


【解决方案1】:

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

对我来说,以下工作。

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)

【讨论】:

  • @gordy - 所以在我看来,这种方法在 SSMS 中并不适用。
  • 这适用于我在使用 CAST() 或 CONVERT() 的 SQL 2008 R2 SP2 (10.50.1600) 和 SQL 2008 SP2 (10.0.5500) 上。
  • 我看到 16,002 个字符后被截断,但仍然比 max 长。 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 将在 Microsoft SQL Server 的未来版本中删除。避免在新的开发工作中使用这些数据类型,并计划修改当前使用它们的应用程序。
  • 在 SQL Server 2014 的 SQL Server Management Studio 中对我不起作用。它在 16.000 个字符后剪切。正如马丁·史密斯所写。
【解决方案2】:

以下解决方法不使用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 进行调整。

【讨论】:

  • +1。但是这种方法对在 XML 中具有特殊含义的字符进行编码。例如,&amp;lt; 替换为 &amp;lt;
  • 不用&lt;root&gt;....也可以写脚本比如:SELECT CAST(@MyLongString AS XML)
  • @aliyouhannaei - 是也不是。你是对的,根元素不是绝对必要的。但是,如果没有 CDATA 部分,您的方法就会开始遇到一些字符串问题。尤其是那些包含 <. xml>
  • @IainElder - 这是一个很好的观点,并且有一个解决方法from Adam Machanic。是这样的:SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH('')。该字符串将被包裹在一个名为“x”的 PI 中,但 PI 不会被包裹在另一个元素中(因为 PATH(''))。
  • 这不适用于很长的文本,即使“检索到的最大字符数 - XML 数据”设置为无限制
【解决方案3】:

这是应该如何完成的:

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

【讨论】:

  • 技术很棒!顺便说一句,起源此技术的实际文章来自 SQLServerCentral.com >>> sqlservercentral.com/scripts/Print/63240
  • 这对我有用,但它也将我的一个字段名称切成两半。所以,如果我用这个方法打印(@string)然后执行(@string),执行失败。
  • 这对我不起作用,因为 PRINT 函数会在不好的地方添加换行符,并且需要更多的清理工作,但这是最接近问题的解决方案。
【解决方案4】:

我刚刚从Ben's 很棒的answer 创建了一个 SP:

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

【讨论】:

  • 太棒了,正是我想要的!
【解决方案5】:

遇到了这个问题,想要一些更简单的...试试以下:

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

【讨论】:

  • 更简单的是SELECT CAST(@STMT AS XML),如另一条评论中所述。产生完全相同的输出,而且确实比为输出创建存储过程简单。
  • @Felix 虽然这会简单得多,但它并不适用于 SQL。转换为 XML 尝试将 SQL 文本转换为 XML。它将 和 & 替换为 <、>和&它不会处理 XML 中不允许的字符。此外,如果你有一个比较 的情况,它认为这是一个元素并抛出一个无效节点错误。
【解决方案6】:

或者简单地说:

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

【讨论】:

  • 您可以在此处扩展 Yavav 的建议并简单地使用许多 8000 字符块...涵盖您最坏的情况...因此,如果 @SQL_InsertQuery 实际上很短,仅打印前几行,其余的只是打印一个空字符串
【解决方案7】:

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

【讨论】:

    【解决方案8】:

    您可以根据脚本长度除以 8000 的计数执行 WHILE 循环。

    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
    

    【讨论】:

    • 如果您查看我的代码,我也在使用 @Pos 变量来查找换行符并相应地打印。那么我怎么能在你的代码中使用它。
    • @peter 您可以使用当前的SUBSTR 并仅查看您当时正在处理的部分并对其进行迭代,或者如果您知道在 8k 之前会有换行符每次限制,然后根据查找换行符执行WHILE
    • 函数是 LEN() 而不是 LENGTH()
    • 我使用print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000)) 打印我的脚本。
    • 我在计数器增量之前插入了打印语句,print substring(@script, (@Counter * 8000) + 1, 8000)
    【解决方案9】:

    你可以用这个

    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
    

    【讨论】:

      【解决方案10】:

      考虑到包装,此过程正确打印出VARCHAR(MAX) 参数:

      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?
      • 在上述评论的回复中,可以通过将@script类型更改为nvarchar来完成。
      【解决方案11】:

      有一个很棒的函数叫做PrintMax written by Bennett Dill

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

      编辑:

      使用 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 Demo

      【讨论】:

        【解决方案12】:

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

        我尝试了一些列出的解决方案,发现 Kelsey 的解决方案适用于较小的 tweeks(@sql 是我的 @script)n.b。 LENGTH 不是一个有效的函数:

        --http://*.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 的轻微修改。是否值得在 msdb 中为 Ben B 的代码创建一个类似存储过程的系统,这样可以在一行中重复使用和调用?

        不幸的是,Alfoks 的代码不起作用,因为那样会更容易。

        【讨论】:

        • 我刚刚将 Ben B 的解决方案添加为临时存储过程。让我的脚本更简洁一些,但我同意它有很多用于调试的行。
        【解决方案13】:

        使用换行符和空格作为一个很好的断点:

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

        【讨论】:

        • 完美运行
        【解决方案14】:
        创建过程 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)); 结尾 打印@print; 选择@text = substring(@text,@i+2,len(@text)); 选择 @i = charindex(@newline, @text); 结尾 打印@文本; 结尾

        【讨论】:

          【解决方案15】:

          这是另一个版本。这个从主字符串中提取要打印的每个子字符串,而不是在每个循环中将主字符串减少 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
          

          【讨论】:

            【解决方案16】:

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

            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
            

            【讨论】:

              【解决方案17】:

              如果源代码不存在LF替换为CRLF的问题,则按照简单的代码输出无需调试。

              --http://*.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; 
              

              【讨论】: