75

このフォーラムのディスカッションによると、SQL Server(2005を使用していますが、これは2000と2008にも適用されます)varcharは、ストアドプロシージャパラメータとして指定した文字列をvarcharの長さにサイレントに切り捨てます。ただし、INSERT実際にはエラーが発生します。例えば。このテーブルを作成する場合:

CREATE TABLE testTable(
    [testStringField] [nvarchar](5) NOT NULL
)

次に、以下を実行すると、次のようになります。

INSERT INTO testTable(testStringField) VALUES(N'string which is too long')

エラーが発生します:

String or binary data would be truncated.
The statement has been terminated.

素晴らしい。データの整合性は維持され、呼び出し元はそれを知っています。次に、それを挿入するためのストアドプロシージャを定義しましょう。

CREATE PROCEDURE spTestTableInsert
    @testStringField [nvarchar](5)
AS
    INSERT INTO testTable(testStringField) VALUES(@testStringField)
GO

そしてそれを実行します:

EXEC spTestTableInsert @testStringField = N'string which is too long'

エラーなし、1行が影響を受けます。testStringField'strin'として行がテーブルに挿入されます。SQL Serverは、ストアドプロシージャのvarcharパラメータをサイレントに切り捨てました。

さて、この振る舞いは時々便利かもしれませんが、私はそれをオフにする方法がないことを収集します。ストアドプロシージャに長すぎる文字列を渡すとエラーが発生するため、これは非常に面倒ですこれに対処するには2つの方法があるようです。

まず、ストアドプロシージャの@testStringFieldパラメータをサイズ6として宣言し、その長さが5を超えているかどうかを確認します。これはちょっとしたハックのようで、刺激的な量の定型コードが含まれます。

次に、すべてのストアドプロシージャのvarcharパラメータをであるvarchar(max)と宣言INSERTし、ストアドプロシージャ内のステートメントを失敗させます。

後者は正常に機能しているように見えるので、私の質問は次のとおりですvarchar(max)。長すぎる文字列が渡されたときに実際にストアドプロシージャを失敗させたい場合は、SQL Serverストアドプロシージャの文字列に常に使用することをお勧めしますか?それもベストプラクティスでしょうか?無効にできないサイレント切り捨ては、私にはばかげているようです。

4

7 に答える 7

32

ただです

ただし、私のチェックの1つは、パラメーターがテーブルの列の長さと一致することを確認することであるため、問題に気づいたことはありません。クライアントコードでも。個人的には、SQLで長すぎるデータが表示されることはないと思います。切り捨てられたデータを見た場合、それを引き起こした原因は明らかです。

varchar(max)の必要性を感じた場合は、データ型の優先順位のために、パフォーマンスの大きな問題に注意してください。varchar(max)はvarchar(n)よりも優先順位が高くなります(最長が最高です)。したがって、このタイプのクエリでは、シークではなくスキャンが取得され、すべてのvarchar(100)値はCASTからvarchar(max)になります。

UPDATE ...WHERE varchar100column = @varcharmaxvalue

編集:

この問題に関する未解決のMicrosoftConnectアイテムがあります。

そして、それはおそらくErland SommarkogのStrict設定(および一致するConnectアイテム)に含める価値があります。

マーティンズのコメントの後、2を編集:

DECLARE @sql VARCHAR(MAX), @nsql nVARCHAR(MAX);
SELECT @sql = 'B', @nsql = 'B'; 
SELECT 
   LEN(@sql), 
   LEN(@nsql), 
   DATALENGTH(@sql), 
   DATALENGTH(@nsql)
;

DECLARE @t table(c varchar(8000));
INSERT INTO @t values (replicate('A', 7500));

SELECT LEN(c) from @t;
SELECT 
   LEN(@sql + c), 
   LEN(@nsql + c), 
   DATALENGTH(@sql + c), 
   DATALENGTH(@nsql + c) 
FROM @t;
于 2011-01-07T17:24:31.830 に答える
17

いつものように、この種の詳細な議論を引き出してくれたStackOverflowに感謝します。私は最近、トランザクションとtry / catchブロックへの標準的なアプローチを使用して、ストアドプロシージャをより堅牢にするために精査しています。私はJoeStefanelliに「アプリケーション側に責任を持たせることを提案する」ことに同意せず、Jezに完全に同意します。「SQLServerに文字列の長さを確認させる方がはるかに望ましい」。ストアドプロシージャを使用する私にとっての重要な点は、ストアドプロシージャがデータベースにネイティブな言語で記述されており、防御の最後の行として機能する必要があるということです。アプリケーション側では、255と256の違いは意味のない数値ですが、データベース環境内では、最大サイズが255のフィールドは256文字を受け入れません。アプリケーション検証メカニズムは、バックエンドデータベースを可能な限り反映する必要がありますが、メンテナンスが難しいため、アプリケーションが不適切なデータを誤って許可した場合に、データベースから適切なフィードバックを提供してもらいたいと思います。そのため、CSVやJSONなどのテキストファイルの束の代わりにデータベースを使用しています。

SPの1つが8152エラーをスローし、別のSPがサイレントに切り捨てられた理由に戸惑いました。私はついに小刻みに動きました:8152エラーをスローしたSPには、関連するテーブル列よりも1文字多く許可するパラメーターがありました。テーブル列はnvarchar(255)に設定されましたが、パラメーターはnvarchar(256)でした。それで、私の「間違い」はgbnの懸念である「大規模なパフォーマンスの問題」に対処しませんか?maxを使用する代わりに、テーブルの列サイズを一貫してたとえば255に設定し、SPパラメーターを1文字長く(たとえば256)に設定することもできます。これにより、サイレント切り捨ての問題が解決され、パフォーマンスが低下することはありません。おそらく、私が考えていなかった他のいくつかの欠点がありますが、それは私にとって良い妥協点のようです。

更新:この手法には一貫性がないのではないかと思います。さらにテストを行うと、8152エラーが発生することがあり、データがサイレントに切り捨てられることがあります。誰かが私がこれに対処するためのより信頼できる方法を見つけるのを手伝ってくれるなら、私は非常にありがたいです。

更新2:このページのPyitoechitoの回答を参照してください。

于 2011-03-19T09:07:48.703 に答える
4

同じ動作がここで見られます:

declare @testStringField [nvarchar](5)
set @testStringField = N'string which is too long'
select @testStringField

私の提案は、ストアドプロシージャを呼び出す前に、アプリケーション側に入力の検証を任せることです。

于 2011-01-07T17:20:33.510 に答える
4

更新:この手法には一貫性がないのではないかと思います。さらにテストを行うと、8152エラーが発生することがあり、データがサイレントに切り捨てられることがあります。誰かが私がこれに対処するためのより信頼できる方法を見つけるのを手伝ってくれるなら、私は非常にありがたいです。

これは、文字列の256番目の文字が空白であるために発生している可能性があります。VARCHARsは、挿入時に末尾の空白を切り捨て、警告を生成するだけです。したがって、ストアドプロシージャは、文字列を256文字に静かに切り捨て、挿入は末尾の空白を(警告付きで)切り捨てます。上記の文字が空白でない場合、エラーが発生します。

おそらく解決策は、ストアドプロシージャVARCHARを空白以外の文字をキャッチするのに適した長さにすることです。VARCHAR(512)おそらく十分に安全でしょう。

于 2013-06-13T13:42:32.297 に答える
1

1つの解決策は次のとおりです。

  1. すべての着信パラメータを次のように変更しますvarchar(max)
  2. 正しいデータ長のspプライベート変数を用意します(すべてをコピーしてパラメーターに貼り付け、最後に「int」を追加するだけです)
  3. 列名が変数名と同じであるテーブル変数を宣言します
  4. 各変数が同じ名前の列に入る行をテーブルに挿入します
  5. 表から内部変数を選択します

このようにして、既存のコードへの変更は、以下のサンプルのように非常に最小限に抑えられます。

これは元のコードです:

create procedure spTest
(
    @p1 varchar(2),
    @p2 varchar(3)
)

これは新しいコードです:

create procedure spTest
(
    @p1 varchar(max),
    @p2 varchar(max)
)
declare @p1Int varchar(2), @p2Int varchar(3)
declare @test table (p1 varchar(2), p2 varchar(3)
insert into @test (p1,p2) varlues (@p1, @p2)
select @p1Int=p1, @p2Int=p2 from @test

着信パラメータの長さが制限を超える場合、文字列を黙って切り落とすのではなく、SQLServerはエラーをスローすることに注意してください。

于 2012-04-19T01:51:42.400 に答える
0

それらの長さをチェックするifステートメントをspにいつでもスローでき、それらが指定された長さより大きい場合はエラーをスローします。ただし、これにはかなり時間がかかり、データサイズを更新する場合は更新するのが面倒です。

于 2011-01-07T21:18:59.777 に答える
-4

これは今日の問題を解決する答えではありませんが、MSSQLが追加を検討するための機能提案が含まれており、この問題を解決します。
これをMSSQLの欠点として指摘することが重要です。そのため、MSSQLの認識を高めることで、問題の解決を支援することができます。
投票したい場合の正式な提案は次のとおりです:
https ://feedback.azure.com/forums/908035-sql-server/suggestions/38394241-request-for-new-rule-string-truncation-error-にとって

私はあなたの欲求不満を共有します。
パラメータに文字サイズを設定することの全体的なポイントは、他の開発者が
    データを渡すときに(Intellisenseを介して)サイズ制限が何であるかを即座に知ることができるようにすることです。
これは、ドキュメントをSprocの署名に直接焼き付けるようなものです。

ほら、わかりました。変数割り当て中の暗黙の変換が原因です。     それでも、この機能を回避することを余儀なくされる
この量のエネルギーと戦うシナリオを費やす正当な理由はありません。 私に言わせれば、SprocsとFunctionsには、     テーブルにデータを入力するときに使用される、パラメーターの割り当て用の同じエンジンルールが設定されている必要があります。これは本当に質問するには多すぎますか?


より大きな文字制限を使用してから、すべてのSprocに各パラメーターの検証を追加するというこれらすべての提案
    はばかげています
切り捨てを確実に回避する唯一の方法は知っていますが、実際にはMSSQLですか?
それがANSI/ISO規格であるかどうかは関係ありません、それはばかげています!

値が長すぎる場合-コードを壊したい-毎回。
次のようになります。goを渡さないで、コードを修正します。
何年にもわたって複数の切り捨てバグが発生し、それらを捕まえることができない可能性があります。
データの整合性を確保するために何が起こりましたか?

すべてのパラメータが検証された後にのみSQLコードが呼び出されると想定するのは危険です。
Webサイトとそれが呼び出すSprocの両方に同じ検証を追加しようとしましたが、
    Webサイトをすり抜けたSprocでエラーが発生しました。それは素晴らしい健全性チェックです!
SprocをWebSite/WebServiceに再利用し、他の
    Sprocs / Jobs / Deployment / Ad-Hocスクリプト(パラメーターを検証するためのフロントエンドがない場合)からも呼び出す場合はどうなりますか?

MSSQLには、最大以外の文字列変数     (Sprocおよび関数のパラメーターとして使用される変数も含む)にこれを適用するNO_TRUNCための" "オプションが必要です。 これは、接続/セッションスコープ:( " "オプションがすべてのクエリに     どのように影響するかなど) または単一の変数に焦点を当てることができます:(     " "が1つのテーブルのテーブルヒントである場合など)。 または、これをデータベース内のすべてのSproc/関数パラメーターに適用するためにオンにしたTrace-FlagまたはDatabaseプロパティ。


TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

NOLOCK

私は何十年にもわたるレガシーコードを覆すことを求めているのではありません。
データベースをより適切に管理するためのオプションをMSに要求するだけです。

于 2019-08-12T22:00:44.240 に答える