Comment imprimer VARCHAR (MAX) à l'aide de Print Statement?

108

J'ai un code qui est:

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)

La longueur du script est d'environ 10 000 caractères et puisque j'utilise une instruction d'impression qui ne peut contenir que 8 000 caractères maximum. J'utilise donc deux instructions d'impression.

Le problème est que lorsque j'ai un script qui compte environ 18 000 caractères, j'utilisais 3 instructions d'impression.

Alors, y a-t-il un moyen de définir le nombre d'instructions d'impression en fonction de la longueur du script?

peter
la source
1
Devez-vous utiliser PRINTou êtes-vous ouvert à d'autres alternatives?
Martin Smith
Je suggère de créer (ou de trouver et de voter) pour un problème sur connect.microsoft.com/SQLServer/Feedback
jmoreno

Réponses:

23

Vous pouvez faire une WHILEboucle en fonction du décompte de la longueur de votre script divisé par 8000.

PAR EXEMPLE:

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
Kelsey
la source
Si vous regardez mon code, j'utilise également la variable @Pos pour trouver le saut de ligne et imprimer en conséquence. Alors, comment pourrais-je utiliser cela dans votre code.
peter
@peter Vous pouvez simplement prendre le courant SUBSTRet ne regarder que la partie que vous traitez à ce moment-là et itérer dessus ou si vous savez qu'il y aura un saut de ligne avant la limite de 8k à chaque fois, alors faites simplement la WHILEligne de recherche des pauses.
Kelsey
@peter pouvez-vous boucler en fonction des sauts de ligne? Par exemple, recherchez un saut de ligne, s'il est trouvé, impression jusqu'au saut de ligne, sous du saut de ligne aux caractères 8k suivants, recherche, impression, nouvelle sous, etc.?
Kelsey
1
La fonction est LEN () pas LENGTH ()
shiggity
8
J'avais l'habitude print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000))d'imprimer mon script.
Lukas Thum
217

Je sais que c'est une vieille question, mais ce que j'ai fait n'est pas mentionné ici.

Pour moi, ce qui suit a fonctionné.

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)
Alfoks
la source
4
@gordy - Il me semble donc que cette méthode ne fonctionne pas vraiment dans SSMS.
Jirka Hanika
1
Cela fonctionne pour moi sur SQL 2008 R2 SP2 (10.50.1600) en utilisant CAST () ou CONVERT (), et sur SQL 2008 SP2 (10.0.5500).
26
Je vois une troncature après 16 002 caractères, encore plus longue que maxsi. 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
Les types de données ntext, texte et image seront supprimés dans une future version de Microsoft SQL Server. Évitez d'utiliser ces types de données dans de nouveaux travaux de développement et prévoyez de modifier les applications qui les utilisent actuellement.
jumxozizi
5
Cela n'a pas fonctionné pour moi dans SQL Server Management Studio pour SQL Server 2014. Il coupe après 16 000 caractères. Comme écrit par Martin Smith.
Jana Weschenfelder
103

La solution de contournement suivante n'utilise pas l' PRINTinstruction. Cela fonctionne bien en combinaison avec SQL Server Management Studio.

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

Vous pouvez cliquer sur le XML renvoyé pour le développer dans le visualiseur XML intégré.

Il y a une limite côté client assez généreuse sur la taille affichée. Allez à Tools/Options/Query Results/SQL Server/Results to Grid/XML datapour l'ajuster si nécessaire.

Jirka Hanika
la source
11
+1. Mais cette méthode encode des caractères qui ont une signification particulière en XML. Par exemple, <est remplacé par &lt;.
Iain Samuel McLean Elder
5
vous pouvez écrire un script sans <root>....comme:SELECT CAST(@MyLongString AS XML)
ali youhannaei
2
@aliyouhannaei - Oui et non. Vous avez raison de dire que l'élément racine n'est pas strictement nécessaire. Mais, sans la section CDATA, votre méthode commence à avoir des problèmes avec certaines chaînes. Surtout ceux qui contiennent <. S'ils ne sont pas XML, la requête génère généralement une erreur. S'ils sont XML, la chaîne peut finir par être reformatée dans une autre forme XML «équivalente».
Jirka Hanika
8
@IainElder - C'est un bon point et il y a une solution de contournement pour cela d'Adam Machanic . Il est ceci: SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH(''). La chaîne sera enveloppée dans un PI appelé "x", mais le PI ne sera pas enveloppé dans un autre élément (à cause de PATH('')).
Jirka Hanika
Cela ne fonctionnera pas pour les textes très longs, même avec "Nombre maximum de caractères récupérés - Données XML" défini sur illimité
Michael Møldrup
39

Voici comment procéder:

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

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

Ben B
la source
1
Excellente technique! BTW, l'article réel à l'origine de cette technique était de SQLServerCentral.com >>> sqlservercentral.com/scripts/Print/63240
Rob.Kachmar
2
Cela a fonctionné pour moi, mais cela a également coupé l'un de mes noms de champ en deux. Donc, si j'utilise cette méthode pour PRINT (@string) puis EXECUTE (@string), EXECUTE échoue.
Johnny Bones
1
Cela ne fonctionne pas pour moi car la fonction PRINT ajoute des sauts de ligne dans les mauvais endroits et nécessiterait plus de nettoyage que cela ne vaut la peine, mais c'est la solution la plus proche du problème.
Randy Burden le
14

Je suis tombé sur cette question et je voulais quelque chose de plus simple ... Essayez ce qui suit:

SELECT [processing-instruction(x)]=@Script FOR XML PATH(''),TYPE
Edyn
la source
5
Plus simple serait SELECT CAST(@STMT AS XML)comme déjà indiqué dans un autre commentaire. Produit exactement la même sortie et est en effet moins compliqué que de créer une procédure stockée pour la sortie.
Felix Bayer
4
@Felix Bien que ce soit beaucoup plus simple, cela ne fonctionne pas tout à fait pour SQL. La conversion en XML tente de convertir le texte SQL en XML. Il remplacera <,> et & par & lt ;, & gt; et & amp; et il ne traitera pas les caractères non autorisés dans XML. De plus, si vous avez une situation dans laquelle vous faites une comparaison de <et puis>, cela pense que c'est un élément et génère une erreur de nœud non valide.
Edyn
12

Cette procédure imprime correctement le VARCHAR(MAX)paramètre en tenant compte de l'encapsulation:

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
Andrey Morozov
la source
cette procédure a un conflit avec les caractères Unicode. comment gérer utf8 par exemple?
mostafa8026
dans la réponse au commentaire ci-dessus, cela peut être fait en changeant le type @script en nvarchar.
mostafa8026
8

Je cherchais à utiliser l'instruction print pour déboguer un SQL dynamique car j'imagine que la plupart d'entre vous utilisent print pour des raisons similaires.

J'ai essayé quelques-unes des solutions répertoriées et j'ai trouvé que la solution de Kelsey fonctionne avec des tweeks mineurs (@sql est mon @script) nb LENGTH n'est pas une fonction valide:

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

Ce code fait comme commenté ajouter une nouvelle ligne dans la sortie, mais pour le débogage, ce n'est pas un problème pour moi.

La solution de Ben B est parfaite et est la plus élégante, bien que pour le débogage il y ait beaucoup de lignes de code, je choisis donc d'utiliser ma légère modification de Kelsey. Cela pourrait valoir la peine de créer un système comme une procédure stockée dans msdb pour le code de Ben B qui pourrait être réutilisé et appelé en une seule ligne?

Le code d'Alfoks ne fonctionne malheureusement pas car cela aurait été plus facile.

Matthew Radford
la source
Je viens d'ajouter la solution de Ben B en tant que procédure stockée temporaire. Garde mes scripts un peu plus propres, mais je suis d'accord que c'est beaucoup de lignes pour le débogage.
Zarepheth
4

Vous pouvez utiliser ceci

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
Marwan Almukh
la source
4

Je viens de créer un SP à partir de la bonne réponse de Ben :

/*
---------------------------------------------------------------------------------
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
Yovav
la source
Merveilleux, exactement ce que je cherchais!
kooch
3
procédure de création dbo.PrintMax @text nvarchar (max)
comme
commencer
    déclare @i int, @newline nchar (2), @print varchar (max); 
    set @newline = nchar (13) + nchar (10);
    sélectionnez @i = charindex (@newline, @text);
    tandis que (@i> 0)
    commencer
        sélectionnez @print = substring (@ text, 0, @ i);
        tandis que (len (@print)> 8000)
        commencer
            sous-chaîne d'impression (@ print, 0,8000);
            sélectionnez @print = substring (@ print, 8000, len (@print));
        fin
        print @print;
        select @text = substring (@ text, @ i + 2, len (@text));
        sélectionnez @i = charindex (@newline, @text);
    fin
    print @text;
fin
Adam Gering
la source
2

Il existe une excellente fonction appelée PrintMax écrite par Bennett Dill .

Voici une version légèrement modifiée qui utilise une procédure stockée temporaire pour éviter la "polution de schéma" (idée de 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;');

Démo DBFiddle

ÉDITER:

En utilisant CREATE OR ALTERnous pourrions éviter deux appels 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;');

Démo db <> fiddle

Lukasz Szozda
la source
2

Utilise les sauts de ligne et les espaces comme bon point de rupture:

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 '   '
BickiBoy
la source
A travaillé parfaitement
Jolley71717
2

Ou simplement:

PRINT SUBSTRING(@SQL_InsertQuery, 1, 8000)
PRINT SUBSTRING(@SQL_InsertQuery, 8001, 16000)
Yovav
la source
0

Voici une autre version. Celui-ci extrait chaque sous-chaîne à imprimer de la chaîne principale au lieu de prendre la réduction de la chaîne principale de 4000 sur chaque boucle (ce qui peut créer beaucoup de très longues chaînes sous le capot - pas sûr).

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
redcalx
la source
0

Cela devrait fonctionner correctement, ce n'est qu'une amélioration des réponses précédentes.

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
vinbhai4u
la source
0

Si le code source n'aura pas de problèmes avec LF à remplacer par CRLF, aucun débogage n'est requis en suivant les sorties de codes simples.

--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; 
Bill Bai
la source
0

Ma version PrintMax pour éviter les mauvais sauts de ligne en sortie:


    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
Ercument Eskar
la source