Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the dangers of BEGIN TRY DROP TABLE?

In a script used for interactive analysis of subsets of data, it is often useful to store the results of queries into temporary tables for further analysis.

Many of my analysis scripts contain this structure:

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;

In SQL Server, temporary tables are connection-scoped, so the query results persist after the initial query execution. When the subset of data I want to analyze is expensive to calculate, I use this method instead of using a table variable because the subset persists across different batches of queries.

The setup part of the script is run once, and following queries (SELECT * FROM #Results is a placeholder here) are run as often as necessary.

Occasionally, I want to refresh the subset of data in the temporary table, so I run the entire script again. One way to do this would be to create a new connection by copying the script to a new query window in Management Studio, I find this difficult to manage.

Instead, my usual workaround is to precede the create statement with a conditional drop statement like this:

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

This statement correctly handles two situations:

  1. On the first run when the table does not exist: do nothing.
  2. On subsequent runs when the table does exist: drop the table.

Production scripts written by me would always use this method because it raises no errors for in the two expected situations.

Some equivalent scripts written by my fellow developers sometimes handle these two situations using exception handling:

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

I believe in the database world it is better always to ask permission than seek forgiveness, so this method makes me uneasy.

The second method swallows an error while taking no action to handle non-exceptional behavior (table does not exist). Also, it is possible that an error would be raised for a reason other than that the table does not exist.

The Wise Owl warns about the same thing:

Of the two methods, the [OBJECT_ID method] is more difficult to understand but probably better: with the [BEGIN TRY method], you run the risk of trapping the wrong error!

But it does not explain what the practical risks are.

In practice, the BEGIN TRY method has never caused problems in systems I maintain, so I'm happy for it to stay there.

What possible dangers are there in managing temporary table existence using BEGIN TRY method? What unexpected errors are likely to be concealed by the empty catch block?

like image 826
Iain Samuel McLean Elder Avatar asked Oct 07 '22 16:10

Iain Samuel McLean Elder


2 Answers

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

If try catch block is inside a transaction, it will cause a failure.

BEGIN
BEGIN TRANSACTION t1;
SELECT 1

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

COMMIT TRANSACTION t1;
END

This batch will fail with an error like this:

Msg 3930, Level 16, State 1, Line 7 The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction. Msg 3998, Level 16, State 1, Line 1 Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.

Books Online documents this behavior:

Uncommittable Transactions and XACT_STATE

If an error generated in a TRY block causes the state of the current transaction to be invalidated, the transaction is classified as an uncommittable transaction. An error that ordinarily ends a transaction outside a TRY block causes a transaction to enter an uncommittable state when the error occurs inside a TRY block. An uncommittable transaction can only perform read operations or a ROLLBACK TRANSACTION. The transaction cannot execute any Transact-SQL statements that would generate a write operation or a COMMIT TRANSACTION.

now replace TRY/Catch with the 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

and run again.Transaction will commit without any error.

like image 79
ClearLogic Avatar answered Oct 10 '22 01:10

ClearLogic


A better solution may be to use a table variable rather than a temporary table

ie:

declare @results table( 
  a INT NOT NULL, 
  b INT NOT NULL, 
  c INT NOT NULL 
); 
like image 30
podiluska Avatar answered Oct 10 '22 01:10

podiluska