簡體   English   中英

為什么聲明為 NVARCHAR(MAX) 的變量會丟棄字符串的塊?

[英]Why is a Variable declared as NVARCHAR(MAX) dropping chunks of the string?

無論出於何種原因,查詢都被構建為字符串並傳遞給另一個存儲過程執行。

查詢量很大。

超過一千行,我們遇到了一個需要我調試它的問題。

該查詢被構建到一個聲明的NVARCHAR(MAX)變量中,但是當我使用以下命令打印它時發生了一些奇怪的事情 -

WHILE @Printed < @ToPrint BEGIN 
    PRINT(SUBSTRING(
        @sql, @Printed, 4000))
    SET @Printed = @Printed + 4000
    PRINT('Printed: ' + CONVERT(VARCHAR, @Printed))
END

在打印消息的某個位置,它只是……丟了一大塊,我不明白為什么。 NVARCHAR(MAX)應該能夠保持 War and Peace 超過 100 次,並且這個查詢不是 War and Peace。

我知道PRINT(...)的限制是一次只能打印 4000 個字符(因此是循環),但這並不能解釋為什么@sql變量只是在某些地方丟失了一個塊。

如果它有幫助,具體來說,在打印前 4,000 個字符后,塊丟棄的位置大約是 1,600 個字符。

為什么要這樣做? 我是否錯過了在查詢開始時設置系統變量(如 NOCOUNT 或 ARITHABORT?我什至不知道它們是做什么的,或者它們是否參與其中。

這對我來說很好用:

DECLARE @sql nvarchar(max) = 
    REPLICATE(CONVERT(nvarchar(max), N'a'), 4000)
  + REPLICATE(CONVERT(nvarchar(max), N'b'), 4000)
  + REPLICATE(CONVERT(nvarchar(max), N'c'), 4000)
  + REPLICATE(CONVERT(nvarchar(max), N'd'), 4000)
  + REPLICATE(CONVERT(nvarchar(max), N'e'), 4000)
  + REPLICATE(CONVERT(nvarchar(max), N'f'), 4000)
  + REPLICATE(CONVERT(nvarchar(max), N'g'), 4000)
  + REPLICATE(CONVERT(nvarchar(max), N'h'), 4000)
  + REPLICATE(CONVERT(nvarchar(max), N'i'), 4000);


PRINT LEN(@sql);  -- characters
PRINT DATALENGTH(@sql); -- bytes
PRINT '';

DECLARE @Printed int = 1, @ToPrint int = LEN(@sql);

WHILE @Printed < @ToPrint BEGIN 
    PRINT(SUBSTRING(
        @sql, @Printed, 4000))
    SET @Printed = @Printed + 4000
    PRINT('Printed: ' + CONVERT(varchar(11), @Printed)) -- *
END

* 始終指定長度

Output 是:

36000
72000

aaaaaaaaaa... 4000 As ...aaa
Printed: 4001
bbbbbbbbbb... 4000 Bs ...bbb
Printed: 8001
cccccccccc... 4000 Cs ...ccc
Printed: 12001
dddddddddd... 4000 Ds ...ddd
Printed: 16001
eeeeeeeeee... 4000 Es ...eee
Printed: 20001
ffffffffff... 4000 Cs ...fff
Printed: 24001
gggggggggg... 4000 As ...ggg
Printed: 28001
hhhhhhhhhh... 4000 Bs ...hhh
Printed: 32001
iiiiiiiiii... 4000 Cs ...iii
Printed: 36001

所以,我認為問題出在其他地方。 無論如何,這是驗證動態 SQL 內容的一種非常草率的方法。 相反,我會這樣做:

SELECT CONVERT(xml, @sql);

Then you can click on the output cell and it opens in an XML text editor for review (you can then copy and paste that output into a query window if you want IntelliSense or any chance in executing, but you'll have to replace encoded characters像&gt; --> > 。我在這里談論這種方法(和另一種方法):

如果您堅持以這種砌磚的方式進行操作,那么此時可能存在某種非打印或字符串終止字符。 如果你說它在字符 5,600 左右,那么你可以這樣做:

DECLARE @i int = 5550, @c nchar(1);
WHILE @i <= 5650
BEGIN
  PRINT '';
  SET @c = SUBSTRING(@sql, @i, 1);
  PRINT '------   ' + RTRIM(@i) + '------:';
  PRINT 'Raw:     ' + @c;
  PRINT 'ASCII:   ' + ASCII(@c);
  PRINT 'UNICODE: ' + UNICODE(@c);
  SET @i += 1;
END

您應該能夠向下掃描並匹配您在損壞的打印 output 中看到的最后一個字符序列。 然后查找Raw:行為空且ASCII:行不是典型( 9101332 )以外的任何內容。

但我不認為這是問題所在。 我將 go 回到之前的評論中,我建議字符串本身就是問題所在。 在問題中,您提到@sql ,但沒有顯示它是如何填充的。 我敢打賭,您添加的某些字符串會被截斷。 需要注意的一些事項:

  • 聲明為varchar / nvarchar但沒有長度的中間變量/參數( 有時會導致 1 個字符的靜默截斷,有時會導致 30 ):

     DECLARE @sql nvarchar(max) = N'SELECT * FROM dbo.table '; DECLARE @where nvarchar = N'WHERE some condition...'; SET @sql += @where; PRINT @sql;

    Output:

     SELECT * FROM dbo.table W
  • 聲明為varchar / nvarchar但太短的中間變量/參數(無論聲明是什么,都會導致靜默截斷):

     DECLARE @sql nvarchar(max) = N'SELECT * FROM dbo.table '; DECLARE @where nvarchar(10) = N'WHERE some condition...'; SET @sql += @where; PRINT @sql;

    Output:

     SELECT * FROM dbo.table WHERE some
  • 帶有NULL的顯式CONCAT ,這會導致靜默刪除任何NULL輸入):

     DECLARE @sql nvarchar(max) = N'SELECT * FROM dbo.table '; DECLARE @where nvarchar(32); DECLARE @orderby nvarchar(32) = N' ORDER BY col1'; SET @sql = CONCAT(@sql, @where, @orderby); PRINT @sql;

    Output:

     SELECT * FROM dbo.table ORDER BY col1

在過於復雜的動態 SQL 一代中很難發現這些東西,因此簡化並嘗試任何方式來划分和征服最終字符串中的主要組件絕不是一個壞主意。 根據您嘗試的復制,我幾乎可以肯定地猜測這是“變量聲明太短”的症狀。 最安全的是確保動態 SQL 字符串的每個輸入都應聲明為nvarchar(max) 除了受元數據約束的實體名稱之外,沒有真正好的理由使用其他任何東西。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM