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

146

我有下面这段代码:

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个字符,由于我正在使用只能容纳最多8000个字符的print语句。因此我使用了两个print语句。

问题是当我有一个18000个字符的脚本时,我会使用3个print语句。

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


1
你必须使用 PRINT 吗?还是你愿意尝试其他的替代方案? - Martin Smith
我建议在https://connect.microsoft.com/SQLServer/Feedback上创建(或查找并投票)问题。 - jmoreno
7
十年后,这些类似的黑客手段仍然是必需的。真是悲哀。 - Greg Woods
18个回答

278

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

对于我而言,以下方法适用(适用于多达16k个字符)

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)

如果你有超过16k个字符,你可以像这样结合@Yovav的答案(64k对于任何人来说应该足够了;)

    print cast( substring(@info, 1, 16000) as ntext )
    print cast( substring(@info, 16001, 32000) as ntext )
    print cast( substring(@info, 32001, 48000) as ntext )
    print cast( substring(@info, 48001, 64000) as ntext )

6
据我看来,这种方法在 SSMS 中似乎并不起作用。 - Jirka Hanika
1
这对我来说是可行的,可以在SQL 2008 R2 SP2(10.50.1600)上使用CAST()或CONVERT(),也可以在SQL 2008 SP2(10.0.5500)上使用。 - user593806
28
我看到在16002个字符后被截断, 但仍然比"max"要长. DECLARE @info NVARCHAR(MAX) = 'A';SET @info = REPLICATE(@info, 16000) + 'BC This is not printed';PRINT @info;PRINT CAST(@info AS NTEXT); - Martin Smith
8
微软 SQL Server 的未来版本将删除 [ntext]、[text] 和 [image] 数据类型。建议新的开发工作中避免使用这些数据类型,并计划修改当前使用它们的应用程序。 - jumxozizi
5
在SQL Server 2014的SQL Server Management Studio中,这对我没用。它在16,000个字符后就被截断了,这正如Martin Smith所写的那样。 - Jana Weschenfelder
显示剩余9条评论

128
以下解决方法不使用PRINT语句,在与SQL Server Management Studio结合使用时效果很好。
SELECT CAST('<root><![CDATA[' + @MyLongString + ']]></root>' AS XML)

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

在显示大小方面,客户端有一个相当宽松的限制。如果需要调整,请转到 工具/选项/查询结果/SQL Server/结果到网格/XML 数据


12
+1. 但是这种方法会对在XML中具有特殊含义的字符进行编码。例如,< 会被替换为 &lt; - Iain Samuel McLean Elder
6
请将文本从英语翻译成中文。仅返回已翻译的文本:您可以编写脚本而不需要 <root>....,例如:SELECT CAST(@MyLongString AS XML) - Ali U
2
@aliyouhannaei - 是的和不是的。你说得对,根元素并不是严格必需的。但是,如果没有CDATA部分,你的方法在处理一些字符串时会遇到麻烦。特别是那些包含<符号的字符串。如果它们不是XML格式,查询通常会出错。如果它们是XML格式,字符串可能会被重新格式化成另一种“等效”的XML形式。 - Jirka Hanika
10
@IainElder - 这是一个很好的观点,关于它有一个解决方法来自Adam Machanic。就是这个:SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH('')。字符串将被包装在名为“x”的PI中,但该PI不会被包装在另一个元素中(因为使用了 PATH(''))。 - Jirka Hanika
1
这对于非常长的文本不起作用,即使将“XML数据检索的最大字符数”设置为无限制。 - Michael Møldrup
显示剩余3条评论

45

以下是应该如何完成此操作的方法:

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 >>> http://www.sqlservercentral.com/scripts/Print/63240/ - Rob.Kachmar
2
这个方法对我有用,但它也把我的一个字段名切成了两半。因此,如果我使用这种方法来打印(@string),然后执行(@string),那么执行将失败。 - Johnny Bones
1
这对我不起作用,因为PRINT函数在错误的位置添加换行符,并且需要进行更多清理工作,但这是最接近问题的解决方案。 - Randy Burden
终于有一个好用的脚本了!我知道你的评论已经是十年前的事了,但做得很好,谢谢! - Marc Chemali

30

你可以使用一个基于脚本长度除以8000的计数的WHILE循环。

例如:

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

注意,如果脚本是SQL代码,则会在语句中间中断...例如:SELECT ISN(这里换行)ULL(SUM(Whatever),0.0) - Stefan Steiger
我强烈建议不要使用这个解决方案。正确的技术如下所示,来自@ben-b。顺便说一句,在SQL Server中,LENGTH()甚至不是一个函数。 - Rob.Kachmar
1
该函数为LEN()而非LENGTH()。 - shiggity
14
我使用了print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000))来打印我的脚本。 - Lukas Thum
1
我在计数器递增之前插入了打印语句,print substring(@script, (@Counter * 8000) + 1, 8000) - José Margaça Lopes
显示剩余5条评论

16

看到这个问题,想要更简单一些的东西... 试试以下方法:

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

6
另一个评论中已经提到,更简单的方法是使用SELECT CAST(@STMT AS XML)。这将产生完全相同的输出,确实比创建用于输出的存储过程要简单。 - Felix Bayer
5
虽然这样做会简单得多,但对于SQL并不完全可行。将其转换为XML需要尝试将SQL文本转换为XML。它将用<、>和&替换<、>和&,且无法处理XML中不允许的字符。此外,如果您比较<和>,它会将其视为元素并引发无效节点错误。 - Edyn

13

我刚刚根据Ben的精彩答案创建了一个SP:

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

12

该过程正确打印出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呢? - mostafa8026
在回复上面的评论中,可以通过将@script类型更改为nvarchar来完成。 - mostafa8026

9

我想使用打印语句来调试一些动态sql,因为我想象大多数人都是用打印语句进行类似的操作。

我尝试了几种解决方案,并发现Kelsey的解决方案可以使用(@sql是我的@script),但需要做一些微小的修改。注意:LENGTH不是一个有效的函数:

--https://dev59.com/PWsz5IYBdhLWcg3wg4Gv
--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

7
或者简单点说:
PRINT SUBSTRING(@SQL_InsertQuery, 1, 8000)
PRINT SUBSTRING(@SQL_InsertQuery, 8001, 16000)

你可以在这里扩展Yavav的建议,简单地使用许多8000个字符块...覆盖您的最坏情况...因此,如果@SQL_InsertQuery实际上很短,只打印前几行,其余的只打印空字符串。 - andrew pate

6
您可以使用这个。
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接