Як надрукувати 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 символів, і оскільки я використовую заяву про друк, яка може містити максимум 8000. Отже, я використовую два заяви про друк.

Проблема полягає в тому, що у мене є сценарій, який становить 18000 символів, тоді я використовував 3 заяви про друк.

То чи існує спосіб, який я міг би встановити кількість друкованих висловлювань залежно від довжини сценарію?


1
Вам доводиться використовувати PRINTабо ви відкриті для інших альтернатив?
Мартін Сміт

Я б запропонував створити (або знайти та проголосувати) для проблеми на connect.microsoft.com/SQLServer/Feedback
jmoreno

Відповіді:


23

Ви можете зробити WHILEцикл на основі підрахунку довжини сценарію, поділеного на 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

Якщо ви дивитесь на мій код, я також використовую змінну @Pos, щоб знайти розрив рядка та надрукувати відповідним чином. Отже, як я можу це використати у вашому коді.
пітер

@peter Ви можете просто взяти струм SUBSTRі подивитися лише на ту частину, з якою ви маєте справу в той час, і повторити це, або якщо ви знаєте, що перед тим, як буде обмежено 8k, кожен раз буде розрив рядка, тоді просто зробіть на WHILEоснові пошуку рядка перерви.
Келсі

@peter Ви можете циклічно базуватись на розривах рядків? Наприклад, шукайте рядок рядків, якщо знайдено друк до розриву рядка, підряд від переривання рядків до наступних 8k символів, пошук, друк, новий субстр тощо?
Келсі

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

1
Це працює для мене на SQL 2008 R2 SP2 (10.50.1600), використовуючи CAST () або CONVERT (), і на SQL 2008 SP2 (10.0.5500).

26
Я бачу усічення після 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);
Мартін Сміт

6
ntext, текстові та графічні типи даних буде видалено у майбутній версії Microsoft SQL Server. Уникайте використання цих типів даних у нових роботах з розробки та плануйте модифікувати додатки, які зараз їх використовують.
jumxozizi

5
Не працював для мене в студії управління SQL Server для SQL Server 2014. Він скорочує після 16 000 символів. Як написав Мартін Сміт.
Jana Weschenfelder

103

У наступному вирішенні не використовується PRINTтвердження. Він добре працює в поєднанні зі студією управління SQL Server.

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

8
@IainElder - Це вдалий момент, і для нього є вирішення Адама Маханіка . Це ось що : SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH(''). Рядок буде обгорнуто в PI під назвою "x", але ІП не буде обгорнуто іншим елементом (через PATH('')).
Jirka Hanika

Це не спрацює з дуже довгими текстами, навіть якщо "Максимально отримані символи - дані XML" встановлені необмежено
Michael Møldrup

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
Чудова техніка! BTW, фактична стаття, яка виникла з цієї методики, була від SQLServerCentral.com >>> sqlservercentral.com/scripts/Print/63240
Rob.Kachmar

2
Це працювало для мене, але воно також розрізало навпіл одне з моїх імен поля. Отже, якщо я використав цей метод PRINT (@string), а потім EXECUTE (@string), ВИКОНАННЯ не вдасться.
Johnny Bones

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. Крім того, якщо у вас є ситуація, коли ви робите порівняння <і потім>, він вважає, що це елемент і видає недійсну помилку вузла.
Едін

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

8

Я хотів використати оператор print для налагодження деякого динамічного sql, як я думаю, що більшість із вас використовує print з простих причин.

Я спробував декілька перерахованих рішень і виявив, що рішення Келсі працює з незначними налаштуваннями (@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)

Цей код, як коментується, додає новий рядок у висновок, але для налагодження це не є проблемою для мене.

Рішення Бена В є ідеальним і є найбільш елегантним, хоча для налагодження - це багато рядків коду, тому я вирішив використовувати мою незначну модифікацію Кельсі. Можливо, варто створити таку систему, як збережена процедура в msdb, для коду Бена Б, який можна було б повторно використовувати і викликати в одному рядку?

Код Альфокса, на жаль, не працює, тому що це було б простіше.


Я щойно додав рішення Бена В як тимчасово збережена процедура. Тримає сценарії трохи чистіше, але я погоджуюся, що це багато рядків для налагодження.
Зарефет

4

Ви можете використовувати це

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

4

Я щойно створив ІП з великої відповіді Бена :

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

Чудово, просто те, що я шукав!
куч

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 = підрядку (@ текст, 0, @ i);
        while (len (@print)> 8000)
        почати
            друкована підрядка (@ print, 0,8000);
            виберіть @print = підрядку (@ print, 8000, len (@print));
        кінець
        print @print;
        виберіть @text = підрядку (@ текст, @ i + 2, len (@text));
        виберіть @i = charindex (@newline, @text);
    кінець
    print @text;
кінець

2

Існує чудова функція під назвою 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 Demo


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

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, заміненим на CRLF, налагодження не потрібно, виконуючи прості виходи з простих кодів.

--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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.