web-dev-qa-db-ja.com

SQL Serverはストアドプロシージャのvarcharをサイレントに切り捨てます

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

_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ステートメントを失敗させます。

後者はうまく機能しているようですので、私の質問は:SQL Serverストアドプロシージャの文字列にvarchar(max) ALWAYSを使用することは良いアイデアですか?実際に文字列が長すぎるときにストアドプロシージャを失敗させたい場合合格?ベストプラクティスでさえありますか?無効にできないサイレントトランケーションは、私には愚かに思えます。

67
Jez

それはただisです。

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

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

UPDATE ...WHERE varchar100column = @varcharmaxvalue

編集:

この問題に関して Microsoft Connectアイテムを開く があります。

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

Martinsのコメントの後、編集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;
29
gbn

いつものように、この種の詳細な議論を引き出してくれたStackOverflowに感謝します。私は最近、ストアドプロシージャを精査して、トランザクションとtry/catchブロックに対する標準的なアプローチを使用して、それらをより堅牢にしました。 Joe Stefanelliは「アプリケーション側に責任を持たせることを提案します」とは反対し、Jezには「SQL Serverに文字列の長さを確認させる方がはるかに望ましい」と完全に同意します。ストアドプロシージャを使用する私にとっての全体のポイントは、それらがデータベース固有の言語で記述されており、最後の防衛線として機能する必要があるということです。アプリケーション側では、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の回答をご覧ください。

15
DavidHyogo

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

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

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

4
Joe Stefanelli

更新:この手法には一貫性がないと思う。さらにテストした結果、8152エラーが発生することがあり、データが暗黙的に切り捨てられることがあります。誰かがこれに対処するより信頼できる方法を見つけるのを手伝ってくれたら、私はとても感謝しています。

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

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

4
Jenius

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

着信パラメータの長さが文字列を静かに切り落とす代わりに制限よりも大きくなる場合、SQL Serverはエラーをスローすることに注意してください。

1
igorp

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

0
DForck42