SQL NVARCHARおよびVARCHARの制限


100

すべて、私には大きな(避けられない)動的SQLクエリがあります。選択基準のフィールド数が原因で、動的SQLを含む文字列は4000文字を超えています。今、私は4000の最大セットがあることを理解していNVARCHAR(MAX)ますが、ステートメントのサーバープロファイラーで実行されたSQLを見て

DELARE @SQL NVARCHAR(MAX);
SET @SQL = 'SomeMassiveString > 4000 chars...';
EXEC(@SQL);
GO

動作しているようです(!?)。これもまた大きい別のクエリの場合、この4000制限(!?)に関連付けられているエラーがスローされます。基本的に、この4000制限の後のすべてのSQLがトリムされ、構文エラーが残ります。プロファイラーにはこれがありますが、この動的SQLクエリは完全に表示されています(!?)。

ここで正確に何が起こっているのですか、この@SQL変数をVARCHARに変換して続行するだけですか?

御時間ありがとうございます。

PS また、これらの大きなクエリを表示するために4000文字以上を出力できると便利です。以下は4000に制限されています

SELECT CONVERT(XML, @SQL);
PRINT(@SQL);

他にクールな方法はありますか?


3
MAXは4000の制限、その1..4000 または MAXの同義語ではありません
Alex K.

質問をC#dllでタグ付けして、これが単なるSQL Serverの質問である場合に設定する理由
HatSoft

編集されました。見つけてくれてありがとう…
MoonKnight 2012

PRINTは、4000文字(Unicodeの場合)または8000文字(シングルバイトエンコーディングの場合)で連結します。これが混乱の原因だと思います。
redcalx 2013年

回答:


235

最大4000のセットがあることを理解しています NVARCHAR(MAX)

あなたの理解は間違っています。nvarchar(max)最大2 GBのデータ(10億の2バイト文字)を(場合によってはそれを超えて)保存できます。

Books onlineのncharとnvarcharから、文法は

nvarchar [ ( n | max ) ]

|文字は、これらの選択肢であることを意味します。つまり nまたはリテラルを指定しますmax

特定を指定することを選択した場合n、これは1から4,000の間である必要がありますが、を使用maxすると、ラージオブジェクトデータ型(ntext非推奨の置換)として定義されます。

実際、SQL Server 2008では、変数に対して2GBの制限を無制限に超える可能性があるようですtempdbここに示されています)。

あなたの質問の他の部分について

連結時の切り捨てはデータ型によって異なります。

  1. varchar(n) + varchar(n) 8,000文字で切り捨てられます。
  2. nvarchar(n) + nvarchar(n) 4,000文字で切り捨てられます。
  3. varchar(n) + nvarchar(n)4,000文字で切り捨てられます。nvarchar優先順位が高いため、結果はnvarchar(4,000)
  4. [n]varchar(max)+ [n]varchar(max)は切り捨てられません(<2GBの場合)。
  5. varchar(max)+ varchar(n)は切り捨てられず(<2GBの場合)、結果はと入力されvarchar(max)ます。
  6. varchar(max)+ nvarchar(n)は切り捨てられず(<2GBの場合)、結果はと入力されnvarchar(max)ます。
  7. nvarchar(max)+ varchar(n)は最初にvarchar(n)入力を変換しnvarchar(n)、次に連結を行います。文字列の長さが varchar(n)4,000文字を超える場合、キャスト先がnvarchar(4000)切り捨てられて切り捨てられます

文字列リテラルのデータ型

あなたが使用している場合はN、プレフィックス文字列をとして<= 4,000文字はタイプされますされてnvarchar(n)いるn文字列の長さがあります。したがって、例N'Foo'として扱われnvarchar(3)ます。文字列が4,000文字を超える場合は、次のように扱われますnvarchar(max)

あなたが使用していない場合はN、プレフィックス文字列をとして<= 8,000文字はタイプされますされてvarchar(n)いるn文字列の長さがあります。より長くvarchar(max)

上記の両方で、文字列の長さがゼロの場合、n1に設定されます。

新しい構文要素。

1.CONCAT機能は、ここでは解決しません

DECLARE @A5000 VARCHAR(5000) = REPLICATE('A',5000);

SELECT DATALENGTH(@A5000 + @A5000), 
       DATALENGTH(CONCAT(@A5000,@A5000));

上記は、両方の連結方法で8000を返します。

2.注意する+=

DECLARE @A VARCHAR(MAX) = '';

SET @A+= REPLICATE('A',5000) + REPLICATE('A',5000)

DECLARE @B VARCHAR(MAX) = '';

SET @B = @B + REPLICATE('A',5000) + REPLICATE('A',5000)


SELECT DATALENGTH(@A), 
       DATALENGTH(@B);`

戻り値

-------------------- --------------------
8000                 10000

@A切り捨てが発生したことに注意してください。

発生している問題を解決する方法。

2つの非maxデータ型を一緒に連結しているため、またはvarchar(4001 - 8000)文字列をnvarchar型付き文字列(偶数nvarchar(max))に連結しているため、切り捨てられています。

2番目の問題を回避するには、すべての文字列リテラル(または少なくとも4001〜8000の範囲の長さの文字列リテラル)の前にを付けNます。

最初の問題を回避するには、割り当てを

DECLARE @SQL NVARCHAR(MAX);
SET @SQL = 'Foo' + 'Bar' + ...;

DECLARE @SQL NVARCHAR(MAX) = ''; 
SET @SQL = @SQL + N'Foo' + N'Bar'

NVARCHAR(MAX)が最初から連結に関与するようにする(各連結の結果として、NVARCHAR(MAX)これも伝播するため)

表示時の切り捨てを回避する

「グリッドへの結果」モードが選択されていることを確認してください。

select @SQL as [processing-instruction(x)] FOR XML PATH 

SSMSオプションを使用すると、XML結果の長さを無制限に設定できます。processing-instructionビットは、次のような文字の問題を回避<として表示します&lt;


2
@Killercam- nvarchar(4000)途中で暗黙のキャストを取得している可能性があります。文字列リテラルが4,000文字未満の場合は、として扱われnvarchar(x)ます。別のnvarchar(x)値を連結すると、アップキャストされるのではなく切り捨てられますnvarchar(max)
Martin Smith

2
@Killercam-あなたはおそらく私の最初のコメントに従って切り捨てられています。がに含まれるDECLARE @SQL NVARCHAR(MAX) = ''; SET @SQL = @SQL + ように割り当てを変更してみてくださいNVARCHAR(MAX)
マーティン・スミス

2
@Killercam-おそらく4,000文字から8,000文字の文字列があります。N扱われます接頭辞nvarchar(max)として、それが扱われることなしにvarchar(n)、その後に暗黙的にキャストnvarchar(4000)あなたに連結するときnvarchar
マーティン・スミス

3
私はこの答えで悟りを開いた
Mudassir Hasan '16

1
素晴らしい答え。本当にありがとう!
ジョンベル

6

わかりましたので、後で行の問題が許容サイズより大きいクエリである場合(クエリが成長し続ける場合に発生する可能性があります)、それをチャンクに分割して文字列値を実行する必要があります。したがって、次のようなストアドプロシージャがあるとします。

CREATE PROCEDURE ExecuteMyHugeQuery
    @SQL VARCHAR(MAX) -- 2GB size limit as stated by Martin Smith
AS
BEGIN
    -- Now, if the length is greater than some arbitrary value
    -- Let's say 2000 for this example
    -- Let's chunk it
    -- Let's also assume we won't allow anything larger than 8000 total
    DECLARE @len INT
    SELECT @len = LEN(@SQL)

    IF (@len > 8000)
    BEGIN
        RAISERROR ('The query cannot be larger than 8000 characters total.',
                   16,
                   1);
    END

    -- Let's declare our possible chunks
    DECLARE @Chunk1 VARCHAR(2000),
            @Chunk2 VARCHAR(2000),
            @Chunk3 VARCHAR(2000),
            @Chunk4 VARCHAR(2000)

    SELECT @Chunk1 = '',
           @Chunk2 = '',
           @Chunk3 = '',
           @Chunk4 = ''

    IF (@len > 2000)
    BEGIN
        -- Let's set the right chunks
        -- We already know we need two chunks so let's set the first
        SELECT @Chunk1 = SUBSTRING(@SQL, 1, 2000)

        -- Let's see if we need three chunks
        IF (@len > 4000)
        BEGIN
            SELECT @Chunk2 = SUBSTRING(@SQL, 2001, 2000)

            -- Let's see if we need four chunks
            IF (@len > 6000)
            BEGIN
                SELECT @Chunk3 = SUBSTRING(@SQL, 4001, 2000)
                SELECT @Chunk4 = SUBSTRING(@SQL, 6001, (@len - 6001))
            END
              ELSE
            BEGIN
                SELECT @Chunk3 = SUBSTRING(@SQL, 4001, (@len - 4001))
            END
        END
          ELSE
        BEGIN
            SELECT @Chunk2 = SUBSTRING(@SQL, 2001, (@len - 2001))
        END
    END

    -- Alright, now that we've broken it down, let's execute it
    EXEC (@Chunk1 + @Chunk2 + @Chunk3 + @Chunk4)
END

2

nvarcharテキストも使用できます。つまり、大規模な文字列の前に単に「N」を付ける必要があるということです。もう制限なし

DELARE @SQL NVARCHAR(MAX);
SET @SQL = N'SomeMassiveString > 4000 chars...';
EXEC(@SQL);
GO

3
これは全体像ではありません... Nプレフィックスを使用し、文字列が4,000文字以下のnvarchar(n)場合、nは文字列の長さとして入力されます。したがって、N'Foo 'はnvarchar(3)、たとえば次のように扱われます。文字列が4,000文字を超える場合、として扱われnvarchar(max)ます。Nプレフィックスを使用せず、文字列が8,000文字以下のvarchar(n)場合、nは文字列の長さとして入力されます。より長くならvarchar(max)。上記の両方のための文字列の長さがゼロ次いでnは1に設定されている場合
MoonKnight

1

受け入れられた答えは私を助けましたが、caseステートメントを含むvarcharの連結を行っている間につまずきました。OPの質問にcaseステートメントが含まれていないことは知っていますが、caseステートメントを含む長い動的SQLステートメントの作成に苦労しながらここにたどり着いた私のような他の人にここに投稿すると役立つと思いました。

文字列連結を伴うcaseステートメントを使用する場合、受け入れられた回答に記載されているルールは、caseステートメントの各セクションに個別に適用されます。

declare @l_sql varchar(max) = ''

set @l_sql = @l_sql +
case when 1=1 then
    --without this correction the result is truncated
    --CONVERT(VARCHAR(MAX), '')
 +REPLICATE('1', 8000)
 +REPLICATE('1', 8000)
end

print len(@l_sql)

0
declare @p varbinary(max)
set @p = 0x
declare @local table (col text)

SELECT   @p = @p + 0x3B + CONVERT(varbinary(100), Email)
 FROM tbCarsList
 where email <> ''
 group by email
 order by email

 set @p = substring(@p, 2, 100000)

 insert @local values(cast(@p as varchar(max)))
 select DATALENGTH(col) as collen, col from @local

result collen > 8000, length col value is more than 8000 chars
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.