Pages

Thursday, August 27, 2015

Exception Handling and Standard of writing SQL query in SQL server

In this article I will discuss about exception handling in SQL and also show you standard of writing SQL query.
We will see below point in this article.
(1)Standard of writing SQL query
(2)Exception handling in SQL stored procedure
(3)Right place for dropping temporary tables in SQL server
(1)   Standard of writing SQL query- Here you can see standard format for SQL Stored procedure.
-- ================================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:        name of author
-- Creation date: 25-05-2015
-- =============================================
 IF EXISTS(SELECT * FROM SYS.OBJECTS WHERE TYPE = 'P' AND NAME = 'NameOfStoredProcedure')
            DROP PROCEDURE NameOfStoredProcedure
GO
CREATE PROCEDURE NameOfStoredProcedure    
@PARAM1 datatype,
@PARAM2 datatype
                        AS
BEGIN
                        SET NOCOUNT ON;
BEGIN TRY
----------------------------------------------------------------------------------------------------------
 --Drop all temproray table here which you have created in overall stored procedure---------
IF OBJECT_ID( 'TEMPDB..#temporarytableName' ) IS NOT NULL
BEGIN
                        DROP TABLE   [#temporarytableName];
END
IF OBJECT_ID( 'TEMPDB..#temporarytableName1' ) IS NOT NULL
BEGIN
                        DROP TABLE   [#temporarytableName];
END
 
 ----------------------------------------------------------------------------------------------------------
  --Create Temproray table here------
SELECT col1,col2,col3            INTO #temporarytableName FROM Tablename
SELECT col1,col2,col3            INTO #temporarytableName1 FROM Tablename1
---------------------------------------------------------
----------------------------------------------------------------------------------------------------------
--Write T-SQL  Query Here-------
----------------------------------------------------------------------------------------------------------
  ------------------------------------------------------------------------------------------------------------
--Again Drop all temproray table here which you have created in overall stored procedure
IF OBJECT_ID( 'TEMPDB..#temporarytableName' ) IS NOT NULL
BEGIN
                        DROP TABLE   [#temporarytableName];
END
IF OBJECT_ID( 'TEMPDB..#temporarytableName1' ) IS NOT NULL
BEGIN
                        DROP TABLE   [#temporarytableName];
END
----------------------------------------------------------------------------------------------------------------
END TRY
BEGIN CATCH
                        INSERT INTO NameofTable_ErrorLog(ERROR_PROCNAME,
ERROR_LINENUM,ERROR_TYPENUM,ERROR_LOGMSG,ERROR_LOGDATE)
                        VALUES(ERROR_PROCEDURE(),ERROR_LINE(), ERROR_NUMBER(),ERROR_MESSAGE(),GETDATE())
                        ----------------------------------------------------------------------------------------------------------
--Again Drop all temproray table in catch block which you have created in overall stored procedure
IF OBJECT_ID( 'TEMPDB..#temporarytableName' ) IS NOT NULL
BEGIN
                        DROP TABLE   [#temporarytableName];
END
IF OBJECT_ID( 'TEMPDB..#temporarytableName1' ) IS NOT NULL
BEGIN
                        DROP TABLE   [#temporarytableName];
END
---------------------------------------------------------------------------------------------------------------------
                        RAISERROR('PROBLEM 
 OCCURED WITH STORE PROCEDURE NAMED NameOfStoredProcedure. THIS WILL BE LOGGED.', 16, 1)
                            
                       
END CATCH        
END

(2)Exception handling in SQL stored procedure- Now I will show you how you can handle exception in T-SQL statement.
-Initially you need to create ERROROCONTAINER table in your database.
 CREATE TABLE ERROROCONTAINER(
       [ROWID] [INT] IDENTITY(1,1) PRIMARY KEY,
       [ERRORPROCNAME] [VARCHAR](50) NOT NULL,
       [ERRORLINENUM] [INT] NOT NULL,
       [ERRORTYPENUM] [INT] NOT NULL,
       [ERRORLOGMSG] [NVARCHAR](MAX) NOT NULL,
       [ERRORLOGDATE] [DATETIME] NOT NULL
)
  -Then we see how to use this table for exception handle. Here I will use this table in catch block of stored procedure.  
 In catch block I have used ERROROCONTAINER table and insert error in this table. Whenever error occurs error logged in this table so that we can easily track error and fix.

Exception Handling in SQL Server

Handling errors in SQL Server became easy with the number of different ways. SQL Server 2005 has introduced the new option that helps to handle the errors effectively. Sometimes we cannot capture the errors which occurred in the end user. Even if we want to know the errors which occurred in the end user, we need to write the code to send it to us. It creates an additional overhead for the server.
SQL Server 2005 introduced TRY...CATCH statement which helps us to handle the errors effectively in the back end. This handling of the exception can provide additional information about the errors.

TRY...CATCH

The TRY...CATCH statement works the same as in the programming languages. First it gets executed in the SQL statement which we have written in the TRY block and if any error occurs, then it will get executed the CATCH block.
BEGIN TRY
// SQL Statements
END TRY
BEGIN CATCH
//Handle the exception details
END CATCH
There are a number of error handling property statements like the following:
ERROR_NUMBER()
ERROR_STATE()
ERROR_SEVERITY()
ERROR_LINE()
ERROR_PROCEDURE()
ERROR_MESSAGE()
Normally, SQL Server stores the default error messages which occurred in the execution in the following system table:
select * from sys.messages
But we can create our own error message details with the help of this exception handling.

Handling the Exception using TRY...CATCH

The below example shows the practical implementation of TRY...CATCH exception handling technique in Northwind database.
USE [NorthWind]
GO

IF OBJECT_ID('dbo.ErrorTracer') IS NOT NULL
BEGIN
 DROP TABLE dbo.ErrorTracer
 PRINT 'Table dbo.ErrorTracer Dropped'
END
GO

CREATE TABLE ErrorTracer
(
  iErrorID INT PRIMARY KEY IDENTITY(1,1),
  vErrorNumber INT,
  vErrorState INT,
  vErrorSeverity INT,
  vErrorLine INT,
  vErrorProc VARCHAR(MAX),
  vErrorMsg VARCHAR(MAX),
  vUserName VARCHAR(MAX),
  vHostName VARCHAR(MAX),
  dErrorDate DATETIME DEFAULT GETDATE()
)

IF OBJECT_ID('dbo.ErrorTracer') IS NOT NULL
BEGIN
 PRINT 'Table dbo.ErrorTracer Created'
END
GO

IF OBJECT_ID('Proc_InsertErrorDetails') IS NOT NULL
BEGIN
 DROP PROCEDURE [dbo].[Proc_InsertErrorDetails]
    PRINT 'Procedure Proc_InsertErrorDetails Dropped'
END
GO

CREATE PROCEDURE Proc_InsertErrorDetails
AS
/*
Purpose    : Insert the error details occurred in the SQL query
Input      : Insert the details which receives from the TRY...CATCH block
Output     : Insert the details of received errors into the ErrorTracer table
Created By : Senthilkumar
Created On : July 17, 2009
*/
BEGIN
  SET NOCOUNT ON 
  SET XACT_ABORT ON
  
  DECLARE @ErrorNumber VARCHAR(MAX)  
  DECLARE @ErrorState VARCHAR(MAX)  
  DECLARE @ErrorSeverity VARCHAR(MAX)  
  DECLARE @ErrorLine VARCHAR(MAX)  
  DECLARE @ErrorProc VARCHAR(MAX)  
  DECLARE @ErrorMesg VARCHAR(MAX)  
  DECLARE @vUserName VARCHAR(MAX)  
  DECLARE @vHostName VARCHAR(MAX) 

  SELECT  @ErrorNumber = ERROR_NUMBER()  
       ,@ErrorState = ERROR_STATE()  
       ,@ErrorSeverity = ERROR_SEVERITY()  
       ,@ErrorLine = ERROR_LINE()  
       ,@ErrorProc = ERROR_PROCEDURE()  
       ,@ErrorMesg = ERROR_MESSAGE()  
       ,@vUserName = SUSER_SNAME()  
       ,@vHostName = Host_NAME()  
  
INSERT INTO ErrorTracer(vErrorNumber,vErrorState,vErrorSeverity,vErrorLine,_
 vErrorProc,vErrorMsg,vUserName,vHostName,dErrorDate)  
VALUES(@ErrorNumber,@ErrorState,@ErrorSeverity,@ErrorLine,@ErrorProc,_
 @ErrorMesg,@vUserName,@vHostName,GETDATE())  
END

IF OBJECT_ID('Proc_InsertErrorDetails') IS NOT NULL
BEGIN
    PRINT 'Procedure Proc_InsertErrorDetails Created'
END
GO

IF OBJECT_ID('Proc_ExceptionHandlingExample') IS NOT NULL
BEGIN
 DROP PROCEDURE [dbo].[Proc_ExceptionHandlingExample]
    PRINT 'Procedure Proc_ExceptionHandlingExample Dropped'
END
GO

CREATE PROCEDURE Proc_ExceptionHandlingExample
AS
BEGIN
/*
Purpose    : Sample procedure for check the Try...Catch
Output     : It will insert into ErrorTracer table if this 
   stored procedure throws any error
Created By : Senthilkumar
Created On : July 17, 2009
*/
   SET NOCOUNT ON
   SET XACT_ABORT ON
   
   BEGIN TRY
      SELECT 15/0
   END TRY
   BEGIN CATCH
      EXEC Proc_InsertErrorDetails
   END CATCH
END

IF OBJECT_ID('Proc_ExceptionHandlingExample') IS NOT NULL
BEGIN
    PRINT 'Procedure Proc_ExceptionHandlingExample Created'
END
GO

EXEC Proc_ExceptionHandlingExample

SELECT * FROM ErrorTracer

Handling Errors in SQL Server 2012

Since the release of SQL Server 2005, you’ve been able to handle errors in your T-SQL code by including a TRY…CATCH block that controls the flow of your script should an error occur, similar to how procedural languages have traditionally handled errors. The TRY…CATCH block makes it easy to return or audit error-related data, as well as take other actions. And within the block—specifically, the CATCH portion—you’ve been able to include a RAISERROR statement in order to re-throw error-related data to the calling application. However, with the release of SQL Server 2012, you now have a replacement for RAISERROR, the THROW statement, which makes it easier than ever to capture the error-related data.
In this article, we’ll look at the TRY…CATCH block used with both the RAISERROR and THROW statements. The examples are based on a table I created in the AdventureWorks2012 sample database, on a local instance of SQL Server 2012. Listing 1 shows the T-SQL script I used to create the LastYearSales table.
USE AdventureWorks2012;
GO

IF OBJECT_ID('LastYearSales', 'U') IS NOT NULL
DROP TABLE LastYearSales;
GO

SELECT
  BusinessEntityID AS SalesPersonID,
  FirstName + ' ' + LastName AS FullName,
  SalesLastYear
INTO
  LastYearSales
FROM
  Sales.vSalesPerson
WHERE
  SalesLastYear > 0;
GO
Listing 1: Creating the LastYearSales table
The script should be fairly straightforward. I use a SELECT…INTO statement to retrieve data from the Sales.vSalesPerson view and insert it into the newly created table. However, to demonstrate how to handle errors, we need to add one more element to our table: a check constraint that ensures the SalesLastYear value is never less than zero. Listing 2 shows the ALTERTABLE statement I used to add the constraint.
ALTER TABLE LastYearSales
ADD CONSTRAINT ckSalesTotal CHECK (SalesLastYear >= 0);
GO
Listing 2: Adding a check constraint to the LastYearSales table
The constraint makes it easy to generate an error when updating the table. All I have to do is try to add a negative amount to the SalesLastYear column, an amount large enough to cause SQL Server to throw an error. Once we’ve created our table and added the check constraint, we have the environment we need for the examples in this article. You can just as easily come up with your own table and use in the examples. Just be sure you have a way of violating a constraint or you come up with another mechanism to generate an error. The goal is to create a script that handles any errors.

Working with the TRY…CATCH Block

Once we’ve set up our table, the next step is to create a stored procedure that demonstrates how to handle errors. The procedure, UpdateSales, modifies the value in the SalesLastYear column in the LastYearSales table for a specified salesperson. It works by adding or subtracting an amount from the current value in that column. Listing 3 shows the script I used to create the procedure. Notice that I include two input parameters—@SalesPersonID and @SalesAmt—which coincide with the table’s SalesPersonID and SalesLastYear columns.
USE AdventureWorks2012;
GO

IF OBJECT_ID('UpdateSales', 'P') IS NOT NULL
DROP PROCEDURE UpdateSales;
GO

CREATE PROCEDURE UpdateSales
  @SalesPersonID INT,
  @SalesAmt MONEY = 0
AS
BEGIN
  BEGIN TRY
    BEGIN TRANSACTION;
      UPDATE LastYearSales
      SET SalesLastYear = SalesLastYear + @SalesAmt
      WHERE SalesPersonID = @SalesPersonID;
    COMMIT TRANSACTION;
  END TRY
  BEGIN CATCH
    IF @@TRANCOUNT > 0
    ROLLBACK TRANSACTION;

    DECLARE @ErrorNumber INT = ERROR_NUMBER();
    DECLARE @ErrorLine INT = ERROR_LINE();
    DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
    DECLARE @ErrorSeverity INT = ERROR_SEVERITY();
    DECLARE @ErrorState INT = ERROR_STATE();

    PRINT 'Actual error number: ' + CAST(@ErrorNumber AS VARCHAR(10));
    PRINT 'Actual line number: ' + CAST(@ErrorLine AS VARCHAR(10));

    RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
  END CATCH
END;
GO
Listing 3: Creating a stored procedure that contains a Try…Catch block
The main body of the procedure definition, enclosed in the BEGIN…END block, contains the TRY…CATCH block, which itself is divided into the TRY block and the CATCH block. The TRY block starts with BEGINTRY and ends with ENDTRY and encloses the T-SQL necessary to carry out the procedure’s actions. In this case, I include an UPDATE statement that adds the @SalesAmount value to the SalesLastYear column. The statement is enclosed in BEGINTRANSACTION and COMMITTRANSACTION statements to explicitly start and commit the transaction. Examples vary in terms of where they include the transaction-related statements. (Some don’t include the statements at all.) Just keep in mind that you want to commit or rollback your transactions at the appropriate times, depending on whether an error has been generated.
If the UPDATE statement runs successfully, the SalesLastYear value is updated and the operation is completed, in which case, the code in the CATCH block is never executed. However, if the UPDATE statement fails and SQL Server generates an error, the transaction is terminated and the database engine jumps to the CATCH block. The CATCH block starts with BEGINCATCH and ends with ENDCATCH and encloses the statements necessary to handle the error.
For the stored procedure in Listing 3, the first step I take in the CATCH block is to roll back the transaction if it is still running. I start by using the @@TRANCOUNT function to determine whether any transactions are still open. @@TRANCOUNT is a built-in SQL Server function that returns the number of running transactions in the current session. In this case, there should be only one (if an error occurs), so I roll back that transaction.
Next, I declare a set of variables based on system functions that SQL Server makes available within the scope of the CATCH block. The functions return error-related information that you can reference in your T-SQL statements. Currently, SQL Server supports the following functions for this purpose:
  • ERROR_NUMBER(): The number assigned to the error.
  • ERROR_LINE(): The line number inside the routine that caused the error.
  • ERROR_MESSAGE(): The error message text, which includes the values supplied for any substitutable parameters, such as times or object names.
  • ERROR_SEVERITY(): The error’s severity.
  • ERROR_STATE(): The error’s state number.
  • ERROR_PROCEDURE(): The name of the stored procedure or trigger that generated the error.
For this example, I use all but the last function, though in a production environment, you might want to use that one as well.
After I declare the variables, I include two PRINT statements that display the values of the @ErrorNumber and @ErrorLine variables (along with some explanatory text). The reason I do this is to demonstrate the difference between what the actual values are and what the RAISERROR statement returns, as you’ll see shortly.
The RAISERROR statement comes after the PRINT statements. The statement returns error information to the calling application. Generally, when using RAISERROR, you should include an error message, error severity level, and error state. The rules that govern the RAISERROR arguments and the values they return are a bit complex and beyond the scope of this article, but for the purposes of this example, I simply pass in the @ErrorMessage, @ErrorSeverity, and @ErrorState variables as arguments.
NOTE: For more information about the RAISERROR statement, see the topic “RAISERROR (Transact-SQL)” in SQL Server Books Online.
That’s basically all you need to do to create a stored procedure that contains a TRY…CATCH block. In a moment, we’ll try out our work. But first, let’s retrieve a row from the LastYearSales table to see what the current value is for salesperson 288. Listing 4 shows the SELECT statement I used to retrieve the data.
SELECT FullName, SalesLastYear
FROM LastYearSales
WHERE SalesPersonID = 288
Listing 4: Retrieving date from the LastYearSales table
Not surprisingly, the statement returns the name and total sales for this salesperson, as shown in Listing 5. As you can see, Rachel Valdez shows over $1.3 million dollars in sales for last year.
FullName  SalesLastYear
Rachel Valdez  1307949.7917
Listing 5: Data retrieved from the LastYearSales table
Now let’s try out the UpdateSales stored procedure. Just for fun, let’s add a couple million dollars to Rachel Valdez’s totals. Listing 6 shows how I use the EXEC statement to call the procedure and pass in the salesperson ID and the $2 million.
EXEC UpdateSales 288, 2000000;
Listing 6: Running the UpdateSales stored procedure
The stored procedure should run with no problem because we’re not violating the check constraint. If we were to execute the SELECT statement again (the one in Listing 4), our results would look similar to those shown in Listing 7. Notice all the extra cash.
FullName  SalesLastYear
Rachel Valdez  3307949.7917
Listing 7: Viewing the updated sales amount in the LastYearSales table
Now let’s look what happens if we subtract enough from her account to bring her totals to below zero. In listing 8, I run the procedure once again, but this time specify -4000000 for the amount.
EXEC UpdateSales 288, -4000000;
Listing 8: Causing the UpdateSales stored procedure to throw an error
As you’ll recall, after I created the LastYearSales table, I added a check constraint to ensure that the amount could not fall below zero. As a result, the stored procedure now generates an error, which is shown in Listing 9.
 (0 row(s) affected)
Actual error number: 547
Actual line number: 9
Msg 50000, Level 16, State 0, Procedure UpdateSales, Line 27
The UPDATE statement conflicted with the CHECK constraint "ckSalesTotal". The conflict occurred in database "AdventureWorks2012", table "dbo.LastYearSales", column 'SalesLastYear'.
Listing 9: The error message returned by the UpdateSales stored procedure
As expected, the information we included in the CATCH block has been returned. But notice that the actual error number (547) is different from the RAISERROR message number (50000) and that the actual line number (9) is different from the RAISERROR line number (27). In theory, these values should coincide. But as I mentioned earlier, the rules that govern RAISERROR are a bit quirky.

Working with the THROW Statement

To simplify returning errors in a CATCH block, SQL Server 2012 introduced the THROW statement. With the THROW statement, you don’t have to specify any parameters and the results are more accurate. You simply include the statement as is in the CATCH block.
NOTE: You can use the THROW statement outside of the CATCH block, but you must include parameter values to do so. For more information about the THROW statement, see the topic “THROW (Transact-SQL)” in SQL Server Books Online.
To demonstrate the THROW statement, I defined an ALTER PROCEDURE statement that modifies the UpdateSales procedure, specifically the CATCH block, as shown in Listing 10.
ALTER PROCEDURE UpdateSales
  @SalesPersonID INT,
  @SalesAmt MONEY = 0
AS
BEGIN
  BEGIN TRY
    BEGIN TRANSACTION;
      UPDATE LastYearSales
      SET SalesLastYear = SalesLastYear + @SalesAmt
      WHERE SalesPersonID = @SalesPersonID;
    COMMIT TRANSACTION;
  END TRY
  BEGIN CATCH
    IF @@TRANCOUNT > 0
    ROLLBACK TRANSACTION;

    DECLARE @ErrorNumber INT = ERROR_NUMBER();
    DECLARE @ErrorLine INT = ERROR_LINE();

    PRINT 'Actual error number: ' + CAST(@ErrorNumber AS VARCHAR(10));
    PRINT 'Actual line number: ' + CAST(@ErrorLine AS VARCHAR(10));

    THROW;
  END CATCH
END;
GO
Listing 10: Altering the UpdateSales stored procedure
Notice that I retain the @ErrorNumber and @ErrorLine variable declarations and their related PRINT statements. I do so only to demonstrate the THROW statement’s accuracy. In actually, I need only to roll back the transaction and specify the THROW statement, without any parameters.
Now let’s execute the stored procedure again, once more trying to deduct $4 million from the sales amount, as shown in Listing 11.
EXEC UpdateSales 288, -4000000;
Listing 11: Causing the UpdateSales stored procedure to throw an error
Once again, SQL Server returns an error. Only this time, the information is more accurate. As you can see in Listing 12, the message numbers and line numbers now match. No longer do we need to declare variables or call system functions to return error-related information to the calling application.
 (0 row(s) affected)
Actual error number: 547
Actual line number: 8
Msg 547, Level 16, State 0, Procedure UpdateSales, Line 8
The UPDATE statement conflicted with the CHECK constraint "ckSalesTotal". The conflict occurred in database "AdventureWorks2012", table "dbo.LastYearSales", column 'SalesLastYear'.
Listing 12: The error message returned by the UpdateSales stored procedure
As you can see, SQL Server 2012 makes handling errors easier than ever. Even if you’ve been using the TRY…CATCH block for a while, the THROW statement should prove a big benefit over RAISERROR. And if you’re new to error handling in SQL Server, you’ll find that the TRY…CATCH block and the THROW statement together make the process a fairly painless one, one well worth the time and effort it takes to learn and implement them.

Saturday, August 22, 2015

Searching and finding a string value in all columns in a SQL Server table

Problem
Sometimes there is a need to find if a string value exists in any column in your table.  Although there are system stored procedures that do a "for each database" or a "for each table", there is not a system stored procedure that does a "for each column".  So trying to find a value in any column in your database requires you to build the query to look through each column you want to search using an OR operator between each column.  Is there any way this can be dynamically generated?
Solution
Once again this is where T-SQL comes in handy along with the use of system tables or system views.  The code below allows you to search for a value in all text data type columns such as (char, nchar, ntext, nvarchar, text and varchar). 
The stored procedure gets created in the master database so you can use it in any of your databases and it takes three parameters:
  • stringToFind - this is the string you are looking for.  This could be a simple value as 'test' or you can also use the % wildcard such as '%test%', '%test' or 'test%'.
  • schema - this is the schema owner of the object
  • table - this is the table name you want to search, the procedure will search all char, nchar, ntext, nvarchar, text and varchar columns in the table
The first thing you need to do is create this stored procedure by copying the below code and executing it in a query window.
USE master
GO
CREATE PROCEDURE sp_FindStringInTable @stringToFind VARCHAR(100), @schema sysname@table sysname AS

DECLARE 
@sqlCommand VARCHAR(8000) DECLARE @where VARCHAR(8000) DECLARE @columnName sysname DECLARE @cursor VARCHAR(8000)
BEGIN TRY
   
SET @sqlCommand 'SELECT * FROM [' @schema '].[' @table '] WHERE'
   
SET @where ''

   
SET @cursor 'DECLARE col_cursor CURSOR FOR SELECT COLUMN_NAME
   FROM ' 
DB_NAME() + '.INFORMATION_SCHEMA.COLUMNS
   WHERE TABLE_SCHEMA = ''' 
@schema '''
   AND TABLE_NAME = ''' 
@table '''
   AND DATA_TYPE IN (''char'',''nchar'',''ntext'',''nvarchar'',''text'',''varchar'')'

   
EXEC (@cursor)

   
OPEN col_cursor   
   
FETCH NEXT FROM col_cursor INTO @columnName   

   
WHILE @@FETCH_STATUS 0   
   
BEGIN   
       IF 
@where <> ''
           
SET @where @where ' OR'

       
SET @where @where ' [' @columnName '] LIKE ''' @stringToFind ''''
       
FETCH NEXT FROM col_cursor INTO @columnName   
   
END   

   CLOSE 
col_cursor   
   
DEALLOCATE col_cursor 

   
SET @sqlCommand @sqlCommand @where
   
--PRINT @sqlCommand
   
EXEC (@sqlCommandEND TRY BEGIN CATCH
   
PRINT 'There was an error. Check to make sure object exists.'
   
IF CURSOR_STATUS('variable''col_cursor') <> -3
   
BEGIN
       CLOSE 
col_cursor   
       
DEALLOCATE col_cursor 
   
END
END 
CATCH
Once the stored procedure has been created you can run some tests.
Here are some tests that were done against the AdventureWorks database.
Find the value 'Irv%' in the Person.Address table.
USE AdventureWorks
GO
EXEC sp_FindStringInTable 'Irv%''Person''Address'

Find the value '%land%' in the Person.Address table.
USE AdventureWorks
GO
EXEC sp_FindStringInTable '%land%''Person''Address'

Find the value '%land%' in the Person.Contact table.
USE AdventureWorks
GO
EXEC sp_FindStringInTable '%land%''Person''Contact'

That's all there is to it.  Once this has been created you can use this against any table and any database on your server.

SQL SERVER : Sargable Queries

Today we are going to look at sargable queries. You might ask yourself, what is this weird term sargable. Sargable comes from searchable argument, sometimes also referred as Search ARGument ABLE. What that means is that the query will be able to use an index, a seek will be performed instead of a scan. In general any time you have a function wrapped around a column, an index won’t be used
Some examples that are not sargable
T-SQL
1
2
3
WHERE LEFT(Name,1) = 'S'
WHERE Year(SomeDate) = 2012
WHERE OrderID * 3 = 33000
Those three should be rewritten like this in order to become sargable
T-SQL
1
2
3
WHERE Name LIKE 'S%'
WHERE SomeDate >= '20120101' AND SomeDate < '20130101'
WHERE OrderID = 33000/3
Let’s create a table, insert some data so that we can look at the execution plan
Create this simple table
T-SQL
1
CREATE TABLE Test(SomeID varchar(100))
Let’s insert some data that will start with a letter followed by some digits
T-SQL
1
2
3
4
5
6
INSERT Test
SELECT LEFT(v2.type,1) +RIGHT('0000' + CONVERT(varchar(4),v1.number),4) 
FROM master..spt_values v1
CROSS JOIN (SELECT DISTINCT LEFT(type,1) AS type 
FROM master..spt_values) v2
WHERE v1.type = 'p'
That insert should have generated 32768 rows
Now create this index on that table
T-SQL
1
CREATE CLUSTERED INDEX cx_test ON Test(SomeID)
Let’s take a look at the execution plan, hit CTRL + M, this will add the execution plan once the query is done running
T-SQL
1
2
3
4
5
SELECT * FROM Test
WHERE SomeID LIKE 's%'
 
SELECT * FROM Test
WHERE LEFT(SomeID,1) = 's'
Here is what the plans looks like
As you can see it is 9% versus 91% between the two queries, that is a big difference
Hit CTRL + M again to disable the inclusion of the plan
Run this codeblock, it will give you the plans in a text format
T-SQL
1
2
3
4
5
6
7
8
9
10
11
12
SET SHOWPLAN_TEXT ON
GO
 
SELECT * FROM Test
WHERE SomeID LIKE 's%'
 
SELECT * FROM Test
WHERE LEFT(SomeID,1) = 's'
GO
 
SET SHOWPLAN_TEXT OFF
GO
Here are the two plans
|–Clustered Index Seek(OBJECT:([master].[dbo].[Test].[cx_test]),
SEEK:([master].[dbo].[Test].[SomeID] >= ‘RĂ¾’ AND [master].[dbo].[Test].[SomeID] < 'T'),
WHERE:([master].[dbo].[Test].[SomeID] like 's%') ORDERED FORWARD)
|--Clustered Index Scan(OBJECT:([master].[dbo].[Test].[cx_test]),
WHERE:(substring([master].[dbo].[Test].[SomeID],(1),(1))='s'))
As you can see the top one while looking more complicated is actually giving you a seek

Making a case sensitive search sargable

Now let’s take a look at how we can make a case sensitive search sargable as well
In order to do a search and make it case sensitive, you have to have a case sensitive collation, if your table is not created with a case sensitive collation then you can supply it as part of the query
Here is an example to demonstrate what I mean
This is a simple table created without a collation
T-SQL
1
2
3
CREATE TABLE TempCase1 (Val CHAR(1))
INSERT TempCase1 VALUES('A')
INSERT TempCase1 VALUES('a')
Running this select statement will return both rows
T-SQL
1
2
SELECT * FROM TempCase1
WHERE Val = 'A' 
Val
—–
A
a
Now create the same kind of table but with a case sensitive collation
T-SQL
1
2
3
CREATE TABLE TempCase2 (Val CHAR(1) COLLATE SQL_Latin1_General_CP1_CS_AS)
INSERT TempCase2 VALUES('A')
INSERT TempCase2 VALUES('a')
Run the same query
T-SQL
1
2
SELECT * FROM TempCase2
WHERE Val = 'A' 
Val
—–
A
As you can see you only get the one row now that matches the case
T-SQL
1
2
SELECT * FROM TempCase1
WHERE Val = 'A' COLLATE SQL_Latin1_General_CP1_CS_AS
Val
—–
A
a
Now let’s take a look at how we can make the case sensitive search sargable
First create this table and insert some data
T-SQL
1
2
3
4
5
6
7
8
9
10
CREATE TABLE TempCase (Val CHAR(1))
 
INSERT TempCase VALUES('A')
INSERT TempCase VALUES('B')
INSERT TempCase VALUES('C')
INSERT TempCase VALUES('D')
INSERT TempCase VALUES('E')
INSERT TempCase VALUES('F')
INSERT TempCase VALUES('G')
INSERT TempCase VALUES('H')
Now we will insert some lowercase characters
T-SQL
1
2
INSERT TempCase
SELECT LOWER(Val) FROM TempCase
Now we will create our real table which will have 65536 rows
T-SQL
1
CREATE TABLE CaseSensitiveSearch (Val VARCHAR(50))
We will do a couple of cross joins to generate the data for our queries
T-SQL
1
2
3
4
5
6
INSERT CaseSensitiveSearch
SELECT t1.val + t2.val + t3.val + t4.val
FROM TempCase t1
CROSS JOIN TempCase t2
CROSS JOIN TempCase t3
CROSS JOIN TempCase t4
Create an index on the table
T-SQL
1
CREATE INDEX IX_SearchVal ON CaseSensitiveSearch(Val)
Just like before, if we run this we will get back the exact value we passed in and also all the upper case and lower case variations
T-SQL
1
2
SELECT * FROM CaseSensitiveSearch
WHERE Val = 'ABCD' 
Here are the results of that query
Val
—–
AbCd
ABcd
Abcd
ABCd
aBCd
abCd
aBcd
abcd
abCD
aBcD
abcD
aBCD
ABCD
AbCD
ABcD
AbcD
If you add the collation to the query, you will get only what matches your value
T-SQL
1
2
SELECT * FROM CaseSensitiveSearch
WHERE Val = 'ABCD' COLLATE SQL_Latin1_General_CP1_CS_AS
Here is the result, it maches what was passed in
Val

ABCD
The problem with the query above is that it will cause a scan. So what can we do, how can we make it perform better? It is simple combine the two queries
First grab all case sensitive and case insensitive values and then after that filter out the case insensitive values
Here is what that query will look like
T-SQL
1
2
3
SELECT * FROM CaseSensitiveSearch
WHERE Val = 'ABCD' COLLATE SQL_Latin1_General_CP1_CS_AS
AND Val LIKE 'ABCD'
AND Val LIKE ‘ABCD’ will result in a seek, now when it also does the Val = ‘ABCD’ COLLATE SQL_Latin1_General_CP1_CS_AS part, it only returns the row that matches your value
If you run both queries, you can look at the plan difference (hit CTRL + M so that the plan is included)
T-SQL
1
2
3
4
5
6
7
8
SELECT * FROM CaseSensitiveSearch
WHERE Val = 'ABCD' COLLATE SQL_Latin1_General_CP1_CS_AS
 
 
 
SELECT * FROM CaseSensitiveSearch
WHERE Val = 'ABCD' COLLATE SQL_Latin1_General_CP1_CS_AS
AND Val LIKE 'ABCD'
Here is the plan
As you can see, there is a big difference between the two
Here is the plan in text as well
T-SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SET SHOWPLAN_TEXT ON
GO
 
SELECT * FROM CaseSensitiveSearch
WHERE Val = 'ABCD' COLLATE SQL_Latin1_General_CP1_CS_AS
 
 
 
SELECT * FROM CaseSensitiveSearch
WHERE Val = 'ABCD' COLLATE SQL_Latin1_General_CP1_CS_AS
AND Val LIKE 'ABCD'
 
GO
 
SET SHOWPLAN_TEXT OFF
GO
|–Table Scan(OBJECT:([tempdb].[dbo].[CaseSensitiveSearch]),
WHERE:(CONVERT_IMPLICIT(varchar(50),[tempdb].[dbo].[CaseSensitiveSearch].[Val],0)=CONVERT(varchar(8000),[@1],0)))
|–Index Seek(OBJECT:([tempdb].[dbo].[CaseSensitiveSearch].[IX_SearchVal]), SEEK:([tempdb].[dbo].[CaseSensitiveSearch].[Val] >= ‘ABCD’
AND [tempdb].[dbo].[CaseSensitiveSearch].[Val] <= 'ABCD'),
WHERE:(CONVERT_IMPLICIT(varchar(50),[tempdb].[dbo].[CaseSensitiveSearch].[Val],0)='ABCD' AND [tempdb].[dbo].[CaseSensitiveSearch].[Val] like 'ABCD') ORDERED FORWARD)