We sometimes may find ourselves in a stressful situation at work, where for example we need to update or delete some records in our database. We’ve all been there. Right after we click that “Execute” button, we realize we forgot to include a WHERE clause and the entire table is wiped instead of only one row. Although things like this can happen to the best of us, we can plan ahead and take preventative measures to make sure, we don’t get negatively impacted by the consequences of such a mistake.
A good practice would be to always write a SELECT statement with a WHERE clause first and execute it to see the number of rows that will be affected. Then, just execute UPDATE or DELETE statement with the same WHERE clause. But we’ll explore some other tips and tricks for “safe SQL coding”.
Let’s take a look at two options in SQL Server Management Studio (SSMS) to prevent accidental data modification/loss from happening.
Let’s take a look at one option in SQL Server to prevent accidental data modification/loss from happening.
When a connection is operating in implicit transaction mode, the instance of the SQL Server Database Engine automatically starts a new transaction after the current transaction is committed or rolled back.
In SSMS there is an option to set Implicit Transactions “ON” in SQL Server options under Tools>Options>Query Execution>SQL Server>ANSI:
To view the current setting for IMPLICIT_TRANSACTIONS, run the following query:
DECLARE @IMPLICIT_TRANSACTIONS VARCHAR(3) = 'OFF'; IF ( (2 & @@OPTIONS) = 2 ) SET @IMPLICIT_TRANSACTIONS = 'ON'; SELECT @IMPLICIT_TRANSACTIONS AS IMPLICIT_TRANSACTIONS;
After setting IMPLICIT_TRANSACTIONS ON, always open a new query window as the setting is applied only for newly open connections, and not for the current one
Let’s delete some data from Sales.Customer table in AdventureWorks2014 database. Open a new query, and run only the SELECT statement first to see what we are about to delete:
Now, run also the DELETE statement, and note that CustomerID 1 is deleted:
If the Implicit Transactions option is switched on, and the query window is simply closed, any open transactions will be rolled back (SSMS issues an IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION statement in the background). The obvious downside of this method is that a user might forget to add a COMMIT to apply the changes at the end. The worst-case scenario could be if changes are committed by someone else who is working on the same database in the same environment, and who will add COMMIT at the end of every script by default.
There are two ways to check if any uncommitted transactions are pending. One is simply by executing the @@TRANCOUNT statement which will return an integer of BEGIN TRANSACTION statements that have occurred on the current connection:
The SELECT @@TRANCOUNT statement returns the number of active transactions for current connection only
Another way is by exiting SSMS in which case a prompt window will appear informing the user that there are some uncommitted transactions pending:
At this point, the user can either commit these transactions or roll them back. Either way, this method is not particularly popular no practical.
Another method is by creating DML prevention triggers on important tables. These triggers fire when a user tries to modify data through a data manipulation language (DML) event such as INSERT, UPDATE, or DELETE statements on a table.
As an example, let’s create a simple delete trigger on Sales.Customer table that will prevent the user from deleting all rows in the table:
USE AdventureWorks2014 GO CREATE TRIGGER [Sales].[uNoDeleteAll] ON [Sales].[Customer] FOR DELETE AS BEGIN DECLARE @Count int SET @Count = @@ROWCOUNT; IF @Count >= (SELECT SUM(row_count) FROM sys.dm_db_partition_stats WHERE OBJECT_ID = OBJECT_ID('Sales.Customer' ) AND index_id = 1) BEGIN RAISERROR('Cannot delete all rows',16,1) ROLLBACK TRANSACTION RETURN; END END GO
Previously, there was no triggers defined on the Sales.Customer table. After refreshing the triggers, the new one is listed:
The good thing about this trigger is that it will allow the user to delete a single row or even more as long as there is a WHERE clause:
However, if the WHERE clause is not specified in the statement the trigger will fire and prevent this action from happening:
Although this method looks promising, it’s far away from ideal solution, as creating a prevention trigger for each table in a database could be a long process depending on database complexity and number of objects.
ApexSQL Complete is a free productivity add-in that integrates seamlessly into both SSMS and VS, and helps users write code faster with its neat features, and much more.
Introduced in ApexSQL Complete R7, the Execution alerts feature has the main goal to provide users ability to define rules for query execution that can prevent data modification/loss.
ApexSQL Complete comes with five built-in alerts and they are located under the Execution alerts tab in the Options dialog:
Each of the built-in rules covers a specific case scenario with different set of rules.
If we execute the DELETE statement without a WHERE clause as before, the warning message will pop-up. At this point, the user has an option to simply click the Cancel button, and stop the query from executing and more importantly from wiping all records in the Sales.Customer table:
If a query is more complex and has many lines of code, the Execution alert message will display the exact line number within the code where the rule is triggered:
This way, the user knows exactly where to look in the code. On an execution of a query, multiple rules can be triggered simultaneously, showing the line numbers for each rule.
If Line numbers option is not turned on in SSMS, press Ctrl + G to open Go To Line dialog box, and enter a particular line number to go to in T-SQL code
Note that no specific setup from the user was needed for using the DELETE statement with no WHERE clause. This case scenario is covered by one of the built-in rules in ApexSQL Complete.
The user has ability to create new rules, or edit existing ones, even the built-in rules. In other words, the user has ability to customize type, condition and value for all alerts:
Unlike triggers in previous example, the user doesn’t have to create them on each object individually. By default, all rules are set to all databases on all servers.
Execution alert rules can be shared between developers by exporting them from one machine to another. This is easily done by using the export/import option under the Execution alerts tab in the Options dialog:
To copy Execution alert rules from one machine to another, simply click the Export button and the Save As dialog will appear. By default, the location where the Alerts.zip file will be saved is: C:\Users\<user_name>\Documents\ApexSQL\ApexSQLComplete, but it can be changed for example to a network shared folder where it will be available to everyone in a company:
From there, on a machine where ApexSQL Complete is already installed, just use the Import button, locate and open the previously saved Alerts.zip file.
For more information on SQL coding execution alerts please see the article: Execution alerts
Another neat ApexSQL Complete feature is the Test mode which allows executing a query without making any changes to a database.
Before executing a query, make sure that Test mode is enabled by clicking the Test mode button from the SSMS toolbar:
Another way to enable the Test mode is through ApexSQL main menu, as well as by using the combination of keys (Ctrl+Alt+Q) on the keyboard:
If ApexSQL Complete toolbar is not visible in SSMS, go to View -> Toolbars and make sure that ApexSQL Complete is checked in the sub menu
When Test mode is enabled, SSMS’s status bar is colored red. This means that any executed query will not make any changes to a database’s actual structure:
The Test mode feature adds the BEGIN TRANSACTION and the ROLLBACK TRANSACTION statements in the code, and rolls back the transaction to the beginning to roll back all modifications made from the beginning of the transaction.
As before, when the DELETE statement with no WHERE clause is executed in the Test mode, the Result grid shows that 19820 rows are ”deleted”:
However, a simple SELECT statement returns all data from the table and shows that nothing is actually affected:
As we said in the very beginning of this article, instead of writing a SELECT query to see the number of rows that will be affected, you could use the Test mode when executing important queries.
- SET IMPLICIT_TRANSACTIONS (Transact-SQL)
- CREATE TRIGGER (Transact-SQL)
- BEGIN TRANSACTION (Transact-SQL)
- ROLLBACK TRANSACTION (Transact-SQL)
August 17, 2017