したがって、MS SQL Server 2008でストアドプロシージャを記述しています。これは非常に長いクエリであり、動的に記述する必要があるため、@Query
という変数を作成し、NVARCHAR(MAX)
型にします。さて、私はtoldになりました。SQLServerの最新バージョンでは、NVARCHAR(MAX)
はばかげた量のデータを保持でき、元の最大4000文字をはるかに超えています。ただし、@Query
は、印刷しようとすると4000文字に切り捨てられます。
DECLARE @Query NVARCHAR(max);
SET @Query = 'SELECT...' -- some of the query gets set here
SET @Query = @Query + '...' -- more query gets added on, etc.
-- later on...
PRINT LEN(@Query) -- Prints out 4273, which is correct as far as I can tell
PRINT @Query -- Truncates value to 4000 characters
EXEC sp_executesql @Query -- totally crashes due to malformed (truncated) query
私は何か間違ったことをしているのですか、それともNVARCHAR(MAX)
の仕組みについて完全に間違っているのですか?
生成された動的SQLを確認するには、テキストモード(ショートカット: Ctrl-T)、次にSELECTを使用
PRINT LEN(@Query) -- Prints out 4273, which is correct as far as I can tell
--SET NOCOUNT ON
SELECT @Query
はどうかと言うと sp_executesql
、これを(テキストモードで)試してください。3つのaaaaa...
は、「SELECT ..」が追加された最長のものです。 Ln... Col..
2番目の出力の終わりに4510を示す右下のステータスバーのインジケータ。
declare @n nvarchar(max)
set @n = REPLICATE(convert(nvarchar(max), 'a'), 4500)
SET @N = 'SELECT ''' + @n + ''''
print @n -- up to 4000
select @n -- up to max
exec sp_Executesql @n
問題はSETステートメントに関連しているようです。式のサイズは4,000バイトを超えることはできません。 4,000文字を超える動的に生成されたステートメントを割り当てようとしている場合は、設定を変更する必要はありません。あなたがする必要があるのはあなたの割り当てを分割することです。ステートメントの長さが6,000文字の場合、論理的なブレークポイントを見つけて、後半を同じ変数に連結します。例えば:
_SET @Query = 'SELECT ....' [Up To 4,000 characters, then rest of statement as below]
SET @Query = @Query + [rest of statement]
_
クエリを通常どおり実行します。つまり、EXEC ( @Query )
連結しているUnicode/nChar/nVarChar値がある場合、SQL Serverは暗黙的に文字列をnVarChar(4000)に変換します。残念ながら、文字列が切り捨てられたり、データがそのために切り捨てられました!
長い文字列(または長いと思われる文字列)を連結する場合always文字列構築をCAST ( '' nVarChar(MAX))のように:
SET @Query = CAST('' as nVarChar(MAX))--Force implicit conversion to nVarChar(MAX)
+ 'SELECT...'-- some of the query gets set here
+ '...'-- more query gets added on, etc.
これがSQL Serverのしくみであると考えるのはなんと苦痛で恐ろしいことでしょう。 :(
複数の変数を使用してコードを複数のSET/SELECT割り当てに分割するというWeb上の他の回避策を知っていますが、上記のソリューションを考えるとこれは不要です。
最大8000文字に達した人は、おそらくUnicodeがなかったために暗黙的にVarChar(8000)に変換されたためでしょう。
説明:
舞台裏で起こっていることは、あなたが割り当てている変数が(MAX)を使用していても、SQL Serverは最初に割り当てている値の右側を評価し、デフォルトでnVarChar(4000)またはVarCharになります(8000)(連結する内容に応じて)。値の計算が完了した後(および値を切り捨てた後)、変数に割り当てるときに値を(MAX)に変換しますが、その時点では遅すぎます。
テキストへの結果では、最大8192文字しか使用できません。
私はこのアプローチを使用します
DECLARE @Query NVARCHAR(max);
set @Query = REPLICATE('A',4000)
set @Query = @Query + REPLICATE('B',4000)
set @Query = @Query + REPLICATE('C',4000)
set @Query = @Query + REPLICATE('D',4000)
select LEN(@Query)
SELECT @Query /*Won't contain any "D"s*/
SELECT @Query as [processing-instruction(x)] FOR XML PATH /*Not truncated*/
最初の問題は、PRINT
ステートメントの制限です。なぜsp_executesql
は失敗しています。ほぼすべての長さの入力をサポートする必要があります。
おそらく、クエリの形式が正しくない理由は、切り捨て以外の何かです。
Printは、varchar(MAX)を8000、nvarchar(MAX)を4000文字に切り捨てます。
しかし;
PRINT CAST(@query AS NTEXT)
クエリ全体を印刷します。
これを使って PRINT BIG
すべてを出力する関数:
IF OBJECT_ID('tempdb..#printBig') IS NOT NULL
DROP PROCEDURE #printBig
GO
CREATE PROCEDURE #printBig (
@text NVARCHAR(MAX)
)
AS
--DECLARE @text NVARCHAR(MAX) = 'YourTextHere'
DECLARE @lineSep NVARCHAR(2) = CHAR(13) + CHAR(10) -- Windows \r\n
DECLARE @off INT = 1
DECLARE @maxLen INT = 4000
DECLARE @len INT
WHILE @off < LEN(@text)
BEGIN
SELECT @len =
CASE
WHEN LEN(@text) - @off - 1 <= @maxLen THEN LEN(@text)
ELSE @maxLen
- CHARINDEX(REVERSE(@lineSep), REVERSE(SUBSTRING(@text, @off, @maxLen)))
- LEN(@lineSep)
+ 1
END
PRINT SUBSTRING(@text, @off, @len)
--PRINT '@off=' + CAST(@off AS VARCHAR) + ' @len=' + CAST(@len AS VARCHAR)
SET @off += @len + LEN(@lineSep)
END
ソース:
今日、同じ問題に遭遇し、その4000文字の制限を超えると、動的クエリを2つの文字列に分割し、クエリの実行時にそれらを連結する必要があることがわかりました。
DECLARE @Query NVARCHAR(max);
DECLARE @Query2 NVARCHAR(max);
SET @Query = 'SELECT...' -- some of the query gets set here
SET @Query2 = '...' -- more query gets added on, etc.
EXEC (@Query + @Query2)
文字列式を使用して動的SQLを作成する際の問題は、SQLが文字列式の評価を4,000文字に制限することです。より長い文字列をnvarchar(max)変数に割り当てることができますが、式に+を含めると(+ CASE ... END +など)、式の結果は4,000文字に制限されます。
これを修正する1つの方法は、+の代わりにCONCATを使用することです。例えば:
SET @sql = CONCAT(@sql, N'
... dynamic SQL statements ...
', CASE ... END, N'
... dynamic SQL statements ...
')
@sqlはnvarchar(max)として宣言されています。