Bagaimana cara mencetak VARCHAR (MAX) menggunakan Print Statement?

108

Saya memiliki kode yaitu:

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)

Panjang Script sekitar 10.000 Karakter dan Karena saya menggunakan Pernyataan cetak yang hanya dapat menampung maksimal 8000. Jadi saya menggunakan dua pernyataan cetak.

Masalahnya adalah ketika saya memiliki skrip yang mengatakan 18000 karakter maka saya biasanya menggunakan 3 pernyataan cetak.

Jadi apakah ada cara agar saya bisa mengatur jumlah pernyataan cetak tergantung pada panjang skrip?

peter
sumber
1
Apakah Anda harus menggunakan PRINTatau terbuka untuk alternatif lain?
Martin Smith
Saya akan menyarankan membuat (atau menemukan dan memilih) untuk masalah di connect.microsoft.com/SQLServer/Feedback
jmoreno

Jawaban:

23

Anda dapat melakukan WHILEperulangan berdasarkan jumlah panjang skrip dibagi 8000.

MISALNYA:

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
sumber
Jika Anda melihat kode saya, saya juga menggunakan variabel @Pos untuk menemukan jeda baris dan mencetak yang sesuai. Jadi Bagaimana saya bisa menggunakannya dalam kode Anda.
peter
@peter Anda dapat mengambil arus SUBSTRdan melihat hanya bagian yang Anda hadapi pada saat itu dan mengulanginya atau jika Anda tahu bahwa akan ada jeda baris sebelum batas 8k setiap kali maka lakukan saja WHILEberdasarkan garis pencarian istirahat.
Kelsey
@ Peter dapatkah Anda mengulang berdasarkan jeda baris? Misalnya mencari pemisah baris, jika ditemukan mencetak hingga jeda baris, substr dari baris baru ke karakter 8k berikutnya, cari, cetak, substr baru dll?
Kelsey
1
Fungsinya LEN () bukan LENGTH ()
shiggity
8
Saya biasa print(substring(@script, @Counter * 8000, (@Counter + 1) * 8000))mencetak skrip saya.
Lukas Thum
217

Saya tahu ini pertanyaan lama, tetapi apa yang saya lakukan tidak disebutkan di sini.

Bagi saya yang berikut ini berhasil.

DECLARE @info NVARCHAR(MAX)

--SET @info to something big

PRINT CAST(@info AS NTEXT)
alfoks.dll
sumber
4
@ Gordy - Jadi menurut saya metode ini tidak benar-benar berfungsi di SSMS.
Jirka Hanika
1
Ini berfungsi untuk saya pada SQL 2008 R2 SP2 (10.50.1600) menggunakan CAST () atau CONVERT (), dan pada SQL 2008 SP2 (10.0.5500).
26
Saya melihat pemotongan setelah 16.002 karakter, masih lebih lama dari sebelumnya 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
6
Tipe data nteks, teks, dan gambar akan dihapus di versi Microsoft SQL Server mendatang. Hindari menggunakan tipe data ini dalam pekerjaan pengembangan baru, dan rencanakan untuk memodifikasi aplikasi yang saat ini menggunakannya.
jumxozizi
5
Tidak berhasil untuk saya di SQL Server Management Studio untuk SQL Server 2014. Pemotongan setelah 16.000 karakter. Seperti yang ditulis oleh Martin Smith.
Jana Weschenfelder
103

Solusi berikut tidak menggunakan PRINTpernyataan tersebut. Ia bekerja dengan baik dalam kombinasi dengan SQL Server Management Studio.

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

Anda dapat mengklik XML yang dikembalikan untuk memperluasnya di XML viewer built-in.

Ada batasan sisi klien yang cukup besar pada ukuran yang ditampilkan. Buka untuk Tools/Options/Query Results/SQL Server/Results to Grid/XML datamenyesuaikannya jika perlu.

Jirka Hanika
sumber
11
+1. Tetapi metode ini menyandikan karakter yang memiliki arti khusus dalam XML. Misalnya <diganti dengan &lt;.
Iain Samuel McLean Penatua
5
Anda dapat menulis skrip tanpa <root>....seperti:SELECT CAST(@MyLongString AS XML)
ali youhannaei
2
@aliyouhannaei - Ya dan tidak. Anda benar bahwa elemen root tidak sepenuhnya diperlukan. Tetapi, tanpa bagian CDATA, metode Anda mulai mengalami masalah dengan beberapa string. Terutama yang mengandung <. Jika bukan XML, kueri biasanya akan error. Jika mereka XML, string mungkin akan diformat ulang menjadi bentuk XML lain yang "setara".
Jirka Hanika
8
@IainElder - Itu poin yang bagus dan ada solusi untuk itu dari Adam Machanic . Ini ini: SELECT @MyLongString AS [processing-instruction(x)] FOR XML PATH(''). String akan dibungkus dalam PI yang disebut "x", tetapi PI tidak akan dibungkus dengan elemen lain (karena PATH('')).
Jirka Hanika
Ini tidak akan berfungsi untuk teks yang sangat panjang, bahkan dengan "Karakter Maksimum yang Diperoleh - data XML" disetel ke tidak terbatas
Michael Møldrup
39

Berikut adalah cara melakukannya:

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

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

Ben B
sumber
1
Teknik hebat! BTW, artikel sebenarnya yang memulai teknik ini berasal dari SQLServerCentral.com >>> sqlservercentral.com/scripts/Print/63240
Rob.Kachmar
2
Ini berhasil untuk saya, tetapi juga memotong salah satu nama bidang saya menjadi dua. Jadi, jika saya menggunakan metode ini untuk PRINT (@string) dan kemudian EXECUTE (@string), EXECUTE gagal.
Johnny Bones
1
Ini tidak berfungsi untuk saya karena fungsi PRINT menambahkan jeda baris di tempat-tempat yang buruk dan akan membutuhkan lebih banyak pembersihan daripada nilainya tetapi ini adalah solusi terdekat untuk masalah tersebut.
Randy Burden
14

Menemukan pertanyaan ini dan menginginkan sesuatu yang lebih sederhana ... Coba yang berikut ini:

SELECT [processing-instruction(x)]=@Script FOR XML PATH(''),TYPE
Edyn
sumber
5
Lebih sederhana akan SELECT CAST(@STMT AS XML)seperti yang telah dinyatakan dalam komentar lain. Menghasilkan keluaran yang persis sama dan memang tidak serumit membuat prosedur tersimpan untuk keluaran.
Felix Bayer
4
@Felix Meskipun itu akan jauh lebih sederhana, itu tidak cukup berfungsi untuk SQL. Mentransmisikan ke XML mencoba mengonversi teks SQL ke XML. Ini akan menggantikan <,>, dan & dengan & lt ;, & gt; dan & amp; dan itu tidak akan menangani karakter yang tidak diperbolehkan dalam XML. Selain itu, jika Anda memiliki situasi di mana Anda melakukan perbandingan <dan kemudian>, itu mengira itu adalah elemen dan melempar kesalahan simpul yang tidak valid.
Edyn
12

Proc ini dengan benar mencetak VARCHAR(MAX)parameter mempertimbangkan pembungkus:

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
sumber
prosedur ini memiliki konflik dengan karakter Unicode. bagaimana menangani utf8 misalnya?
mostafa8026
dalam membalas komentar di atas, dapat dilakukan dengan mengubah tipe @script menjadi nvarchar.
mostafa8026
8

Saya ingin menggunakan pernyataan print untuk men-debug beberapa sql dinamis karena saya membayangkan sebagian besar dari Anda menggunakan print untuk alasan yang sama.

Saya mencoba beberapa solusi yang terdaftar dan menemukan bahwa solusi Kelsey bekerja dengan tweeks kecil (@sql adalah @script saya) nb PANJANG bukanlah fungsi yang valid:

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

Kode ini berfungsi seperti yang dikomentari, menambahkan baris baru ke dalam output, tetapi untuk debugging ini bukan masalah bagi saya.

Solusi Ben B sempurna dan paling elegan, meskipun untuk debugging ada banyak baris kode jadi saya memilih untuk menggunakan sedikit modifikasi Kelsey. Mungkin layak untuk membuat sistem seperti prosedur tersimpan di msdb untuk kode Ben B yang dapat digunakan kembali dan dipanggil dalam satu baris?

Sayangnya kode Alfoks tidak berfungsi karena itu akan lebih mudah.

Matthew Radford
sumber
Saya baru saja menambahkan solusi Ben B sebagai prosedur tersimpan sementara. Menjaga skrip saya sedikit lebih bersih, tetapi saya setuju bahwa itu banyak baris untuk debugging.
Zarepheth
4

Anda bisa menggunakan ini

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

Saya baru saja membuat SP dari jawaban hebat 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
sumber
Luar biasa, persis apa yang saya cari!
kooch
3
buat prosedur dbo.PrintMax @text nvarchar (max)
sebagai
mulai
    mendeklarasikan @i int, @newline nchar (2), @print varchar (max); 
    setel @newline = nchar (13) + nchar (10);
    pilih @i = charindex (@newline, @text);
    sementara (@i> 0)
    mulai
        pilih @print = substring (@ teks, 0, @ i);
        sementara (len (@print)> 8000)
        mulai
            cetak substring (@ print, 0,8000);
            pilih @print = substring (@ print, 8000, len (@print));
        akhir
        cetak @ print;
        pilih @text = substring (@ text, @ i + 2, len (@text));
        pilih @i = charindex (@newline, @text);
    akhir
    cetak @ teks;
akhir
Adam Gering
sumber
2

Ada fungsi hebat bernama PrintMax yang ditulis oleh Bennett Dill .

Berikut adalah versi yang sedikit dimodifikasi yang menggunakan prosedur tersimpan temp untuk menghindari "polusi skema" (ide dari 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;');

Demo DBFiddle

EDIT:

Menggunakan CREATE OR ALTERkami dapat menghindari dua panggilan 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 <> biola Demo

Lukasz Szozda
sumber
2

Menggunakan Line Feed dan spasi sebagai break point yang baik:

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
sumber
Bekerja dengan sempurna
Jolley71717
2

Atau sederhananya:

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

Ini versi lain. Yang ini mengekstrak setiap substring untuk dicetak dari string utama daripada mengurangi string utama sebanyak 4000 pada setiap loop (yang mungkin membuat banyak string sangat panjang di bawah kap - tidak yakin).

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

Ini harus berfungsi dengan baik ini hanya perbaikan dari jawaban sebelumnya.

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

Jika kode sumber tidak memiliki masalah dengan LF yang akan diganti oleh CRLF, Tidak diperlukan debugging dengan mengikuti keluaran kode sederhana.

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

Versi PrintMax saya untuk mencegah jeda baris yang buruk pada keluaran:


    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
sumber