5

データのサブセットのインタラクティブな分析に使用されるスクリプトでは、クエリの結果を一時テーブルに保存して、さらに分析できるようにすると便利なことがよくあります。

私の分析スクリプトの多くには、次の構造が含まれています。

CREATE TABLE #Results (
  a INT NOT NULL,
  b INT NOT NULL,
  c INT NOT NULL
);

INSERT INTO #Results (a, b, c)
SELECT a, b, c
FROM ...

SELECT *
FROM #Results;

SQL Server では、一時テーブルは接続スコープであるため、クエリの結果は最初のクエリの実行後も保持されます。分析したいデータのサブセットの計算にコストがかかる場合、テーブル変数を使用する代わりにこの方法を使用します。これは、サブセットがクエリのさまざまなバッチ間で保持されるためです。

スクリプトのセットアップ部分は 1 回実行され、次のクエリ (SELECT * FROM #Resultsはプレースホルダーです) が必要なだけ実行されます。

時々、一時テーブルのデータのサブセットを更新したいので、スクリプト全体をもう一度実行します。これを行う 1 つの方法は、Management Studio の新しいクエリ ウィンドウにスクリプトをコピーして新しい接続を作成することですが、これは管理が難しいと思います。

代わりに、私の通常の回避策は、次のような条件付きドロップ ステートメントを作成ステートメントの前に置くことです。

IF OBJECT_ID(N'tempdb.dbo.#Results', 'U') IS NOT NULL
BEGIN
  DROP TABLE #Results;
END;

このステートメントは、次の 2 つの状況を正しく処理します。

  1. テーブルが存在しない最初の実行時: 何もしません。
  2. テーブルが存在する場合の後続の実行時: テーブルを削除します。

私が作成したプロダクション スクリプトでは、2 つの予想される状況でエラーが発生しないため、常にこの方法を使用します。

私の仲間の開発者によって書かれたいくつかの同等のスクリプトは、例外処理を使用してこれら 2 つの状況を処理することがあります。

BEGIN TRY DROP TABLE #Results END TRY BEGIN CATCH END CATCH

データベースの世界では、許しを求めるよりも常に許可を求める方が良いと信じているため、この方法は不安になります。

2 番目の方法は、例外的でない動作 (テーブルが存在しない) を処理するためのアクションを実行せずに、エラーを飲み込みます。また、テーブルが存在しない以外の理由でエラーが発生する可能性もあります。

賢明なフクロウは同じことについて警告しています:

2 つの方法のうち、[ OBJECT_IDmethod] の方が理解しにくいですが、おそらくより優れています。[ BEGIN TRYmethod] を使用すると、間違ったエラーをトラップするリスクがあります。

しかし、実際のリスクが何であるかは説明されていません。

実際には、BEGIN TRY私が保守しているシステムでこの方法が問題を引き起こしたことは一度もないので、そのままにしておいてよかったと思っています。

BEGIN TRYメソッドを使用して一時テーブルの存在を管理する場合、どのような危険が考えられますか? 空の catch ブロックによって隠される可能性のある予期しないエラーは何ですか?

4

4 に答える 4

3

What possible dangers? What unexpected errors are likely to be concealed?

try catch ブロックがトランザクション内にある場合、エラーが発生します。

BEGIN
BEGIN TRANSACTION t1;
SELECT 1

BEGIN TRY DROP TABLE #Results END TRY BEGIN CATCH END CATCH

COMMIT TRANSACTION t1;
END

このバッチは次のようなエラーで失敗します。

メッセージ 3930、レベル 16、状態 1、行 7 現在のトランザクションをコミットできず、ログ ファイルに書き込む操作をサポートできません。トランザクションをロールバックします。メッセージ 3998、レベル 16、状態 1、行 1 バッチの最後に、コミットできないトランザクションが検出されました。トランザクションはロールバックされます。

Books Onlineは、この動作を次のように文書化しています。

コミット不可能なトランザクションと XACT_STATE

TRY ブロックで生成されたエラーによって現在のトランザクションの状態が無効になった場合、そのトランザクションはコミット不可能なトランザクションとして分類されます。通常、TRY ブロックの外側でトランザクションを終了させるエラーは、TRY ブロックの内側でエラーが発生した場合、トランザクションをコミット不能状態にします。コミット不可能なトランザクションは、読み取り操作または ROLLBACK TRANSACTION のみを実行できます。トランザクションは、書き込み操作または COMMIT TRANSACTION を生成する Transact-SQL ステートメントを実行できません。

TRY/Catch を Test Method に置き換える

BEGIN
BEGIN TRANSACTION t1;
SELECT 1

IF OBJECT_ID(N'tempdb.dbo.#Results', 'U') IS NOT NULL
BEGIN
  DROP TABLE #Results;
END;

COMMIT TRANSACTION t1;
END

トランザクションはエラーなしでコミットされます。

于 2012-09-14T01:14:39.940 に答える
1

より良い解決策は、一時テーブルではなくテーブル変数を使用することです

すなわち:

declare @results table( 
  a INT NOT NULL, 
  b INT NOT NULL, 
  c INT NOT NULL 
); 
于 2012-09-12T14:04:10.723 に答える
0

あなたの質問ではありませんが、おそらく見落とされているのは、一時テーブルで使用されるリソースです。リソースを拘束しないように、常にスクリプトの最後にテーブルをドロップします。テーブルに 100 万行を入れるとどうなるでしょうか。次に、スクリプトの開始時にテーブルをテストして、前回の実行でエラーが発生し、テーブルが削除されなかったという条件を処理します。temp を再利用したい場合は、少なくとも行を消去してください。

テーブル変数は別のオプションです。軽量で、制限があります。クエリ オプティマイザはテーブル変数を処理しないため、クエリ結合で使用する場合はテーブル変数を避けてください。

SQL ドキュメント:

1 つのストアド プロシージャまたはバッチ内に複数の一時テーブルが作成される場合は、それらに異なる名前を付ける必要があります。

複数のユーザーが同時に実行できるストアド プロシージャまたはアプリケーションでローカル一時テーブルが作成される場合、データベース エンジンは、異なるユーザーによって作成されたテーブルを区別できる必要があります。データベース エンジンDatabase Engine は、数値のサフィックスを各ローカル一時テーブル名に内部的に追加することによってこれを行います。tempdb の sysobjects テーブルに格納される一時テーブルの完全な名前は、CREATE TABLE ステートメントで指定されたテーブル名と、システムによって生成された数値サフィックスで構成されます。サフィックスを許可するために、ローカル一時名に指定される table_name は 116 文字を超えることはできません。

DROP TABLE を使用して明示的に削除しない限り、一時テーブルはスコープ外になると自動的に削除されます。

ストアド プロシージャで作成されたローカル一時テーブルは、ストアド プロシージャが終了すると自動的に削除されます。このテーブルは、そのテーブルを作成したストアド プロシージャによって実行されるネストされたストアド プロシージャから参照できます。テーブルを作成したストアド プロシージャを呼び出したプロセスは、テーブルを参照できません。

他のすべてのローカル一時テーブルは、現在のセッションの終了時に自動的に削除されます。

テーブルを作成したセッションが終了し、他のすべてのタスクがそれらの参照を停止すると、グローバル一時テーブルは自動的に削除されます。タスクとテーブルの間の関連付けは、1 つの Transact-SQL ステートメントが存続している間だけ維持されます。これは、作成セッションの終了時にアクティブにテーブルを参照していた最後の Transact-SQL ステートメントの完了時に、グローバル一時テーブルが削除されることを意味します。

于 2012-09-12T16:43:17.250 に答える
0

tryブロックは思わぬ問題を隠してしまうので危険だとも思います。一部のプログラミング言語は、選択したエラーのみをキャッチでき、予期しないエラーはキャッチできません。プログラミング言語にこの機能がある場合は、それを使用してください (T-SQL は特定のエラーをキャッチできません)。

try catchあなたのシナリオでは、このブロックを使用して、あなたとまったく同じように成文化すると説明できます。

望ましい動作は次のとおりです。

begin try
   drop table #my_temp_table
end try
begin catch __table_dont_exists_error__
end catch

しかし、これは存在しません!次に、次のように書くことができます。

begin try
   drop table #my_temp_table
end try
begin catch 
  declare @err_n int, @err_d varchar(MAX)
  SELECT 
    @err_n = ERROR_NUMBER() ,
    @err_d = ERROR_MESSAGE() ;
  IF @err_n <> 3701 
     raiserror( @err_d, 16, 1    )     
end catch

これにより、テーブルの削除エラーが「テーブルが存在しない」と異なる場合にイベントが発生します。

あなたの問題では、このコードはすべて価値がないことに注意してください。しかし、他のアプローチにも役立ちます。あなたの問題の場合、エレガントな解決策は、存在する場合にのみテーブルをドロップするか、テーブル変数を使用することです。

于 2012-09-12T14:04:30.680 に答える